JavaScript中对闭包的理解

145 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

JavaScript中对闭包的理解

概念

一个函数和它的周围状态的引用捆绑在一起构成闭包。

闭包可以让内部函数访问外部函数。

在JavaScript中,每当函数被创建时,就会在函数生成时创建一个新的闭包。

作用

让子函数能够使用父函数的变量或函数,将其生命周期延长。

让其变量或函数能够一直存在,在外部可以访问函数内部的值

理解

初步理解

 function father() {
     var a = 10
 ​
     return function child() {
         console.log(a) // 10
     }
 }
 // 就相当于 inside = funcion child(){...}
 var inside = father()
 inside()

我们将上面的代码拆解来进行讲解:

  • 我们在 father 函数中定义了一个变量和函数 child,并将函数 child 返回了;
  • 在函数 child 的内部创建了一个输出语句来将变量 a 进行输出;
  • 在函数外部,让变量 inside 接收了 father 函数;
  • 就相当于把函数 father 内部的 child 函数赋值给了 inside 变量;
  • 之后执行 inside ,就相当于执行了函数内部的 child 函数;
  • 但在child 函数内部并没有变量 a,那么它是怎么输出变量a 的值的呢;
  • 其实它就是到它的上级作用域 father 中找到的;
  • 如果没有找到的话,它就会继续向上级作用域寻找,直到找到全局作用域为止。

像上面这种现象就是闭包,函数嵌套着函数,内部函数使用着外部函数的某些变量。

进阶理解

 function sum() {
     for (var i = 0; i < 6; i++) {
         setTimeout(function () {
             console.log(i)
         }, 4000)
     }
 }
 sum()

在上面的代码中我们可以发现在一个循环中定义了一个定时器,那么它的输出结果是什么呢?

输出结果:

为什么不是我们想象中的输出:0,1,2,3,4,5 呢?

下面我们就来解释:

  • 这里我们定义了一个 for 循环和定时器;
  • 而定时器又是异步执行的,咱们的for循环执行又是非常迅速的;
  • 这样导致的结果就是,for循环很快的执行完了,此时的i=6,不满足条件就退出了循环,而这是的定时器还久久未执行;
  • 当定时器执行的时候就只有从 i=6这个条件开始重复输出6次了。

那我们如何解决这个问题呢?

其实很简单,我们引入闭包,从而来保存变量 i 不被销毁。

如下所示:

匿名函数

让定时器成为了一个闭包,使其 i 得到了保存,没有被销毁。

 function sum() {
     for (var i = 0; i < 6; i++) {
         // 匿名函数自调用
        // x 的值就是 i
         (function (x) {
             setTimeout(function () {
                 console.log(x)
             }, 4000)
         })(i)
     }
 }
 sum()

使用ES6中的let

在ES6中新增了let,它可以使定义的变量成为块级作用域。

let 是特别适合在循环中使用的。

这里是让for循环成为了块级作用域,让内部一级一级的执行,从而保证了 i 不被销毁。

 function sum() {
     for (let i = 0; i < 6; i++) {
         setTimeout(function () {
             console.log(i)
         }, 4000)
     }
 }
 sum()

内存泄露

在使用闭包时,会导致变量一直存在;

这时候我们就需要解除对象的引用;

也就是将对象设置为null,;

来让其内存在适当时候被回收,从而加快浏览器的访问速度。