美文网首页
闭包详细图解

闭包详细图解

作者: 灯下草虫鸣314 | 来源:发表于2020-11-20 19:53 被阅读0次

    前言

    在我们了解了执行上下文EC之后,我们应该知道了,js执行的正常流程。但是我们在工作、面试时经常会遇到闭包这个奇怪的东西,今天我们就来详细的了解一下。闭包是如何形成的,闭包在ECS中是如何执行的。

    正文

    作用域和作用域链

    讲闭包之前我们需要先了解作用域和作用域链。

    作用域: 表示一个变量的可用范围,从而避免不同范围的变量之间相互干扰。

    作用域分为两种:

    • 全局作用域:在浏览器端全局作用域就是window,他可以重复使用,随处可用。但是全局作用的定义的变量容易被污染
    • 函数作用域:函数声明的时候会自动创建函数作用域对象scope,他指向函数声明时的作用域。而每个函数调用时创建的AO对象前会先创建当前函数的作用域,并创建parent对象通过函数声明时的作用域对象scope指向他函数的父级作用域。他不会污染全局,但是不会重复使用。函数执行完毕后会随着函数执行上问出栈=>AO对象销毁而销毁

    作用域链

    变量取值到创建这个变量的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向通过parent对象去父作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

    闭包

    为什么会使用闭包?

    我们先总结下全局变量和局部变量的优缺点。

    • 全局变量:可以重用、但是会造成全局污染而且容易被篡改
    • 局部变量:仅函数内使用不会造成全局污染也不会被篡改、不可以重用
      从上面可以看出全局变量和局部变量的优缺点刚好是相对的。闭包的出现正好结合了全局变量和局部变量的优点。

    闭包是怎么形成,在ECS如何执行的呢?

    我们以一下代码为例:

    function add(){
      var n = 0
      return function(){
        n++
        console.log(n)
      }
    }
    const num = add()
    num()
    num()
    num()
    

    代码开始执行时

    首先创建全局执行上下文,并将全局执行上下文压入执行上下文栈底。

    那么全局执行上下文活动对象AO=>window对象中会有以下内容


    image

    执行顺序:

    • 全局函数add => 内存地址 => add(){....}
    • 全局变量num => undefined
      函数在声明时会自动创建一个scope对象指向创建时的作用域(AO活动对象),此时作用域是window,所以声明的add函数的socpe指向window

    add()执行

    image

    执行顺序:

    • add执行上下文入栈,创建add活动对象AO
      • add函数AO对象中有:
      • 局部变量n => 0
      • parent => window对象(parent根据add函数声明时scope)
    • add执行返回一个函数,函数声明创建scope对象指向当前作用域 => add活动对象AO
    • 返回的函数,函数被全局变量num引用

    add()执行完成,出栈

    image.png

    add()执行完成。add的执行上下文出栈。

    此时问题来了。

    add()执行上下文已经出栈了。但是add的活动对象AO没有被释放!

    为什么呢? 根据图片的我标注出来的红线,这里形成了一个循环引用。所有add的活动对象AO无法被释放。这样就形成了一个闭包。

    num()执行

    image

    执行顺序:

    • num执行上下文入栈。创建num活动对象AO
      • num函数AO对象中:
      • parent => add函数AO对象(parent根据num函数声明时scope)
    • 执行n++,发现当前函数作用域中没有n变量,则根据作用域链向上查找。找到add的AO中有n变量。n++

    num()执行完成,出栈

    image

    num()执行完成。num的执行上下文出栈。num的活动对象AO被垃圾回收。
    接下来的num()执行,和上述步骤相同。

    到这里闭包函数是如何执行也就解释清楚了。

    闭包的缺点

    通过上面的分析。我们发现没生成一个闭包就会有一个活动对象AO常驻内存,不会被销毁,所以有可能会导致内存溢出。

    如何取消闭包

    通过上面的分析。我们发现生成闭包就是因为有一个循环引用。导致活动对象AO无法被销毁垃圾回收。所以我们如果想要取消闭包,只需要打断循环引用就好了: num = null。

    相关文章

      网友评论

          本文标题:闭包详细图解

          本文链接:https://www.haomeiwen.com/subject/cqjjiktx.html