原生js:闭包

71 阅读2分钟

一、什么是闭包

MDN

一个函数和对其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包

JavaScript高级程序设计第三版

闭包是指有权访问另一个函数作用域中的变量的函数

JavaScript高级程序设计第四版

闭包是指那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的

二、闭包的保持

一般情况下,函数作用域中的AO会在函数执行完被销毁。在闭包中,因为引用了父级函数作用域中的变量,一旦闭包被return出去被全局变量接收,那就意味着被引用的这个变量不会被销毁,从而会导致父级函数已经执行完了,而它的AO却被保存在闭包的作用域链上,这种情况就是闭包的保持。

三、闭包的作用

实例解释闭包在[[Scopes]]中发挥的作用

      function test1() {
        function test2() {
          var b = 2
          console.log(a)
          a = 2
          console.log(a)
        }
        var a = 1
        return test2
      }
      var c = 3
      var test3 = test1()
      test3()

这段代码在预编译和[[Scopes]]中是怎样流转的

        test1函数声明时,系统生成[[Scopes]]属性,保存test1的作用域链,顶端存储GO
        GO: {
          c: undefined,
          test1: f test1(){},
          test3: undefined
        }

        test1函数执行时(前一刻),函数test2被声明
        test1AO: {
          a: undefined,
          test2: f test2(){}
        }
        GO: {
          c: undefined,
          test1: f test1(){},
          test3: undefined
        }
        此时test2的作用域链和test1保持一致

        test1执行完,由于test2被返回到外部,并且被全局变量test3接收,因此test1AO没有被销毁,test2中依然可以访问test1AO
        test1AO: {
          a: 1,
          test2: f test2(){}
        }
        GO: {
          c: 3,
          test1: f test1(){},
          test3: undefined
        }

        test3执行时(前一刻),实质上是执行了test2,test2在自己的作用域链上增加了test2AO
        test2AO: {
          b: undefined
        }
        test1AO: {
          a: 1,
          test2: f test2(){}
        }
        GO: {
          c: 3,
          test1: f test1(){},
          test3: f test2(){}
        }

        test3执行完,test2AO被销毁,但test1AO任然存在,并且test2可以访问到test1AO
        test1AO: {
          a: 2,
          test2: f test2(){}
        }
        GO: {
          c: 3,
          test1: f test1(){},
          test3: f test2(){}
        }