js 作用域与闭包

letconst 出现之前,js 是没有会计作用域的, 在我们使用 var 定义变量的时候,是有可能会导致变量提升的。

比如

1
2
3
4
5
for (var i=0;i<2;i++) {
console.log(i)
}

console.log(i);// 2

这段代码最后会输出2,这是因为var 导致了变量 i 的提升,在有了 let之后我们用let就不会有这个问题。

1
2
3
4
5
for (let i=0;i<2;i++) {
console.log(i)
}

console.log(i);// error i is not defined

作用域链

我们先看看这段代码

1
2
3
4
5
6
7
8
9
10
11
12

var a = 1;
function test(){
console.log(a)
var b = 2;
function inner(){
console.log(b)
}
inner();
}
test();
console.log(b)

上面这段代码会依次输出,1,2,error b is not defined,

在js中函数是有自己的作用域的,函数内部定义的变量,外部是无法直接访问到的,只有函数内部才能够直接访问。

函数内部在调用 inner() ,在访问 b 这个变量的时候,会先看自己的作用域中是否有这个变量,发现没有,然后再查找 test 的作用域,发现了这个变量,然后就输出这个变量。

这里外部无法访问到test中的b,所以在函数执行完之后,b会被销毁掉。

闭包

我们先来看一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function countResult(){
var count = 0;
return function () {
count ++;
console.log(cout)
}
}

var tmp = countResult();
var tmp2 = countResult();
tmp(); // 1
tmp2(); // 1
tmp(); // 2

这里虽然我们无法直接访问到 countResult 中的变量count,但是我们通过返回的函数可以间接的访问到count,tmp保持着对count的引用,所以tmp 中的count不会被销毁,我们再新生成一个tmp2,可以发现,tmp和tmp2之间的作用域是不同的,他们都有自己单独的局部作用域。

闭包的作用主要有两点:

  1. 隔离作用域
  2. 使局部变量能够常驻内存

同样闭包带来的问题也非常明显:

  1. 闭包会使得函数中的变量都被保存到内存中,导致内存消耗很大。在使用闭包的时候,退出函数之前,我们需要将一些不使用的局部变量全部删除。

js 的垃圾回收机制

引用计数法

应有计数通过计算变量的引用数来回收垃圾,如果一个值的引用数为0则将其回收

引用计数法会存在一个问题。

1
2
3
4
5
6
7
8

function test(){
var a = {};
var b= {};
a.b=b;
b.a=a;
}
test();

test 中的 a和b互相引用了对方,但是这里执行完之后本该销毁这两个变量,如果使用引用计数法的话,就不会销毁这两个变量而导致内存泄露。

标记清除法

标记清除法是通过根节点向下访问,如果能访问到的都不会被清除,在浏览器中根节点就 window, 这样就避免了循环引用导致的内存泄露问题。