JS闭包

72 阅读5分钟

边理解边写的,写的很烂,作用域链和闭包的区别都没讲清楚(改天有空了再改改)

定义

  • 闭包也叫词法闭包或者是函数闭包;
  • 是在支持头等函数的编程语言中,实现的词法绑定的一种技术(补充:词法作用域就是在写代码的时候就已经决定了变量的作用域,与之相对的是动态作用域);
  • 闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境
  • 闭包和函数最大的区别是,当捕捉到闭包时,它的自由变量(内部函数访问到的外部变量)会在捕捉时被确定,这样即使脱离了上下文,它也能照常运行。
    广义上来说,js中的函数都是闭包
    狭义上来说,js中一个函数访问外层作用域的变量那么就是一个闭包

闭包内存图

内存图之代码解析过程

在执行上下文栈执行代码之前,先对代码进行解析 图片.png 创建对象,并且将全局定义的变量和函数关联到GO里面,这里没有画scope作用域链,因为是全局代码,只有全局作用域。

内存图之代码执行

函数createAdder执行

图片.png

  • 创建AO对象,解析函数里的变量,count赋值为5,return adder,拿到即将赋值给adder5的adder地址
  • 创建函数执行上下文,并且将它压入执行上下文栈
  • 确定scope对象0x031,在这个对象里保存了此函数可访问到的作用域地址(即AO地址)
  • 确定作用域的this指向(没画)

函数adder执行

图片.png

  • 将createadder的执行上下文出栈
  • 执行全局代码,var adder5=0x02
  • 执行adder5(10)
  • 创建AO对象,解析AO对象里的变量,将num赋值为10,通过闭包找到count的值
  • 确定scope,确定this指向 createadder在执行完后,不释放相关内存,满足了内存管理的可达性。

!!!在上面的代码中使用了闭包,但是并没有讲清楚什么是闭包

在网上查了很多资料,理解了很多文章,有人认为闭包就是函数可以访问到外部作用域的变量,此函数和外部作用域一起组成闭包,还有人觉得闭包是函数里面嵌套函数,并且嵌套函数可以访问到外部函数的变量,这就是闭包.....还有很多其他的观点,上面两个是我学习过程中看到的也是把我搞混淆的观点,于是我买了套《你不知道的javascript》,书里的观点比较清晰明确,所以有人看到我这篇文章,并且刚好也对这些东西有疑问的话,我推荐你去买本书看,比看网上乱七八糟的文章要好,比如我这篇,谁要是看了肯定懵逼。
zhuanlan.zhihu.com/p/22486908 就是看了这篇文章里和评论区的观点,促使我买这本书的,网上的东西太多太杂了,还是看书更好理解。

重新理解闭包

在我上面的例子中,确实使用到了闭包,但是我在对他们进行解释时并没有体现出闭包的特点,而是用作用域链的方式来解释的,实际上作用域链是闭包的一种规则,但只是闭包的一部分而已。

function createAdder(count){
            function adder(num){
                return num+count
            }
            adder(10)
        }
        createAdder(5)

我们来看这段代码,函数createAdder()里面嵌套了一个函数adder(),并且在adder函数里也访问了外部作用域的变量。

图片.png

通过调试看到,在浏览器里,认为adder函数和它的外部作用域是闭包,但是在《你不知道的javascript》里认为,“在技术上,这也许是闭包,但是确切的说,这并不是,最准确的解释是,adder()对count的引用的方法是词法作用域的查找规则(也就是作用域链查找),而这些规则只是闭包的一部分”。 下面再来看一段代码

function createAdder(count){
            function adder(num){
                return num+count
            }
            return adder
        }
        var adder5 =createAdder(5)
        adder5(10)//15

图片.png 浏览器调试结果,认为adder5函数和它的外部作用域是闭包。
在这段代码里,函数作为返回值被return出去,并且在外部词法作用域里调用了这个函数,上面那段代码adder的调用是在createAdder里面的,当createAdder执行完毕后,createAdder作用域就会被销毁,而这段代码里,createAdder执行完毕后,依旧没被销毁,因为adder5函数在外部作用域执行,而adder5拥有涵盖createAdder作用域的闭包,所以createAdder执行完毕后,并没有被销毁,它的生命周期被延迟的长了,以供adder5在之后运行时引用,这个引用就叫闭包,像这样的闭包是明确且容易观察的。 再看一段代码:

function wait(message){
            setTimeout(function timer (){
                console.log(message);
            },1000)
        }
        wait('hello,closure')

这段代码里,定义了一个函数wait,内部有一个定时器,时间间隔为1s,timer引用了wait的参数,在wait函数执行完1s后,wait函数作用域并没有消失,因为timer回调仍然拥有对wait作用域的闭包。

我对《你不知道的javascript》中定义的理解,闭包是基于词法作用域书写代码时产生的自然结果,闭包不需要刻意创建,可以根据自己的意愿来识别闭包,内部函数能够延长外部作用域的生存周期,这个函数及封闭这个函数的外部环境就是闭包