【JavaScript】闭包

53 阅读4分钟

1. 什么是闭包?

闭包是一种现象。闭包是指在 JavaScript 中,内部函数可以访问外部函数作用域中的变量,并且在外部函数执行结束后仍然保持对这些变量的引用。换句话说,只要函数中使用了外部的数据,就创建了闭包。而作用域链是实现闭包的手段。

image.png

image.png

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

自动形成的闭包,是会被销毁掉的。

image.png

因为没有引用,所以闭包也会被销毁。

function eat() {
    let food = '米饭'
    console.log(food)
}
eat()
console.log(food)

方法执行结束之后,上下文被销毁,food 变量也会跟着消失。

然后我们修改一下:

function eat() {
    let food = '米饭'
    return function () {
        console.log(food)
    }
}
let fd = eat()
fd()  // 米饭

此时因为 food 一直被引用,没有被垃圾回收机制回收掉。所以仍然可以打印 food。

所以闭包的特点就是:

  • 可以让外部环境访问到函数内部的局部变量
  • 可以让局部变量持续保存下来,不随着它的上下文环境一起销毁

从而根据此特征,可以解决全局变量污染的问题。

var name = 'GlobalName'
var init = (function () {
    var name = 'initName'
    function callName() {
        console.log(name)
    }
    return function (){
        callName()
    }
})()
init() // initName

总结一下:

  1. 闭包是一个封闭的空间,里面存储了在其他地方会引用到的该作用域的值,在JavaScript中是通过作用域链来实现的闭包。
  2. 只要在函数中使用了外部的数据,就创建了闭包,这种情况下所创建的闭包,我们在编码时是不需要去关心的。
  3. 我们还可以通过一些手段手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着它的上下文一起销毁。

但是,在谈闭包之前需要先知道作用域和垃圾回收。

作用域

  • 作用域是一个独立的地盘,不会让变量暴露出去,不同作用域下的同名变量不会有冲突。
  • 作用域在定义时就确定,并且不会改变。
  • 如果当前作用域中没有查到值,就向上级作用于中去查,直到找到全局作用域,这个查找过程形成的链就是作用域链。

垃圾回收

  • Javascript 执行环境会负责管理代码执行过程中使用的内存,其中就涉及到一个垃圾回收机制。
  • 垃圾收集器会定期(周期性)找出那些不再继续使用的变量,只要该变量不再使用了,就会被垃圾收集器回收,然后释放其内存。如果该变量还在使用,那么就不会被回收。

2. 闭包的经典问题

for (var i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i)
    }, 1000)
}

此时会打印三个4 。原因就是因为闭包。setTimeout 访问了外部变量 i,形成闭包。i 变量只有一个,循环到 4 时循环结束,而又因为 setTimeout 等待 1s 执行, i 变成 4 的时候才会打印输出。

为了解决这个问题,我们有两种方法。

方法一:

for (var i = 1; i <= 3; i++) {
    (function (index) {
        setTimeout(function () {
            console.log(index)
        }, 1000)
    })(i)

}

让 setTimeout 中的匿名函数不再访问外部变量,而是访问自己的变量,相当于有了自己的变量 index,而这个 index 在每次传入的时候都是不一样的,从而不再创建闭包,使用自己作用域中的 变量,打印 1,2,3。

方法二:

for (let i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i)
    }, 1000)
}

使用 let。

总结一下:

  • 闭包可以解决一个全局变量污染的问题。
  • 如果是自动产生的闭包,我们无需操心闭包的销毁,而如果是手动创建的闭包,可以把被引用的变量设置为null,即手动清除变量,这样下次JavaScript垃圾回收器在进行垃圾回收时,发现此变量已经没有任何引用了,就会把设为 null 的量给回收了。