闭包
概念
函数以及函数能访问到的变量(自由变量)的总和就是闭包
闭包是一种语法特性(既不是技术也不是语法)
创建闭包:执行函数时,只要函数中使用到了外部的数据,就自然创建了闭包
作用域链就是实现闭包的手段
要不要放入闭包,取决于该变量有没有被引用
优点:
避免污染全局环境
可以通过提供对局部变量的间接访问接口,维持(保护)一个变量,不随上下文的销毁而销毁
缺点:闭包在使用不当的情况下会造成内存泄漏
经典问题
for(var i = 0; i <= 2; i++) {
setTimeout(() => console.log(i), 500)
}
// 打印结果 3 3 3
// 首先 var i 使得 i 成为了全局变量
// timeout 回调函数与其访问的 i 形成了一个闭包,访问的 i 与上层作用域的 i 是相同的
// 在 500 ms 后,timeout 所访问到的 i 已经变成了 3,所以打印了 3 次 3
使用立即执行函数手动构造一个闭包,将每次循环的 i 维持到 timeout 回调函数的闭包中
for(var i = 0; i <= 2; i++) {
(function (index){
setTimeout(() => console.log(index), 500)
})(i)
}
ES6 的块级作用域,每次 timeout 回调函数所访问到的 i 都是当前作用域下的 i
for(let i = 0; i <= 2; i++) {
setTimeout(() => console.log(i), 500)
}
闭包的销毁
如果是自然形成的闭包,无需担心闭包的销毁;而如果是手动创建的闭包,可以把被引用的变量设置为 null
,即手动清除变量,然后交给垃圾回收自动销毁即可。
例如下面使用高阶函数手动创建的闭包
function eat () {
var food = "apple"
return function() {
console.log(food)
}
}
var f = eat()
// 此时虽然 eat 函数执行完毕了,按理说其内部的空间应该被释放
// 但这里 f 变量引用了 eat 函数返回的匿名函数
// 而该函数又与 eat 内部的变量 food 形成了一个闭包,因此 food 变量不会被垃圾回收
f()
// 可以手动将 f 置为 null
f = null