在 let
和 const
出现之前,js 是没有会计作用域的, 在我们使用 var
定义变量的时候,是有可能会导致变量提升的。
比如
1 | for (var i=0;i<2;i++) { |
这段代码最后会输出2,这是因为var 导致了变量 i 的提升,在有了 let之后我们用let就不会有这个问题。
1 | for (let i=0;i<2;i++) { |
作用域链
我们先看看这段代码
1 |
|
上面这段代码会依次输出,1,2,error b is not defined,
在js中函数是有自己的作用域的,函数内部定义的变量,外部是无法直接访问到的,只有函数内部才能够直接访问。
函数内部在调用 inner()
,在访问 b 这个变量的时候,会先看自己的作用域中是否有这个变量,发现没有,然后再查找 test 的作用域,发现了这个变量,然后就输出这个变量。
这里外部无法访问到test中的b,所以在函数执行完之后,b会被销毁掉。
闭包
我们先来看一下代码
1 |
|
这里虽然我们无法直接访问到 countResult
中的变量count,但是我们通过返回的函数可以间接的访问到count,tmp保持着对count的引用,所以tmp 中的count不会被销毁,我们再新生成一个tmp2,可以发现,tmp和tmp2之间的作用域是不同的,他们都有自己单独的局部作用域。
闭包的作用主要有两点:
- 隔离作用域
- 使局部变量能够常驻内存
同样闭包带来的问题也非常明显:
- 闭包会使得函数中的变量都被保存到内存中,导致内存消耗很大。在使用闭包的时候,退出函数之前,我们需要将一些不使用的局部变量全部删除。
js 的垃圾回收机制
引用计数法
应有计数通过计算变量的引用数来回收垃圾,如果一个值的引用数为0则将其回收
引用计数法会存在一个问题。
1 |
|
test 中的 a和b互相引用了对方,但是这里执行完之后本该销毁这两个变量,如果使用引用计数法的话,就不会销毁这两个变量而导致内存泄露。
标记清除法
标记清除法是通过根节点向下访问,如果能访问到的都不会被清除,在浏览器中根节点就 window
, 这样就避免了循环引用导致的内存泄露问题。