跳到主要内容

闭包

概念

函数以及函数能访问到的变量(自由变量)的总和就是闭包

闭包是一种语法特性(既不是技术也不是语法)

创建闭包:执行函数时,只要函数中使用到了外部的数据,就自然创建了闭包

作用域链就是实现闭包的手段

要不要放入闭包,取决于该变量有没有被引用

优点:

  • 避免污染全局环境

  • 可以通过提供对局部变量的间接访问接口,维持(保护)一个变量,不随上下文的销毁而销毁

缺点:闭包在使用不当的情况下会造成内存泄漏

经典问题

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