资讯专栏INFORMATION COLUMN

结合作用域,执行上下文图解闭包

msup / 2482人阅读

摘要:作用域链所谓作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证当前执行环境对符合访问权限的变量和函数的有序访问。当我们在执行函数的时候,需要的变量,在自己的作用域内找不到,便会顺着作用域链往上找,直到找到全局作用域。

一 作用域相关
      作用域是一套规则,用来管理引擎如何查找变量。在es5之前,js只有全局作用域及函数作用域。es6引入了块级作用域。但是这个块级别作用域需要注意的是不是{}的作用域,而是let,const关键字的块作用域。

1作用域
1.1 全局作用域
      在全局环境下定义的变量,是挂载在window下的。如下代码所示:

1.2 函数作用域

      在函数内定义的变量,值在函数内部才生效,在函数外引用会报RefrenceError的错误

      注意区分RefrenceError及TypeError。RefrenceError是在作用域内找不到,而TypeError则是类型错误。如果只是定义了变量a 直接调用便会报TypeError的错误。

1.3 块作用域

      es新增的关键字let,const是作用在块级作用域。但是在js内{}形成的块,是不具有作用域的概念的。如下所示,虽然for循环有一个{}包裹的块,但是在块外面还是可以访问i的。

2 作用域链

      所谓作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证当前执行环境对符合访问权限的变量和函数的有序访问。而作用域的最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

      如上图所示,会形成一个inner作用域到outer作用域到全局作用域的作用域链。当我们在执行inner函数的时候,需要outName的变量,在自己的作用域内找不到,便会顺着作用域链往上找,直到找到全局作用域。在这个例子中,往上查找到outer作用域的时候便找到了。

      简单测试1:如下图所示的代码,大家觉得会输出什么呢?

      虽然fn的调用是在show内调用的,但是因为fn所在的作用域是全局作用域,它的x的值会顺着作用域链去全局作用域中啊,即x会输出10。这里需要注意的一点是,变量的确定是在函数定义时候确定的,而不是函数运行时。

二 执行上下文相关

      函数每次被调用时,都会产生一个新的执行上下文环境。全局上下文是存在栈中的。而处于栈顶的全局上下文一旦执行完就会自动出栈。如下图所示的代码。

      首先是全局上下文入栈,然后开始执行可执行代码。遇到outer(),激活outer()的上下文;

      第二步,outer的上下文入栈。开始执行outer内的可执行代码,直到遇到inner()。激活inner()的上下文;

      第三步,inner的上下文入栈。开始执行inner内的可执行代码。执行完毕之后inner出栈。

      第四步,inner的上下文出栈。outer内继续执行可执行代码。如果一直没有其他的执行上下文,执行完毕即可出栈;

      第五步,outer的上下文出栈。

      ps:全局上下文只有浏览器关闭的时候才会出栈。

      那我们已经直到了全局上下文的宏观入栈出栈的概念。具体的全局上下文包括哪些内容,具体做了什么操作呢?

      其实,执行上下文分为准备阶段和执行阶段。

      1.在执行上下文的准备阶段,会有以下步骤:

            1.1 创建变量对象:初始化arguments,函数声明提升,变量声明提升等

            1.3 建立作用域链

     2.而在执行上下文的执行阶段,会有以下步骤:

            2.1 变量赋值

            2.2 函数引用

            2.3 确定this指向

            2.4 执行代码

      而在变量对象的创建过程,会经历以下的步骤。

            1.创建arguments对象。也就是当前上下文中的参数;

            2.检查当前上下文的函数声明,即用function关键字声明的函数;

            3.检查当前上下文的变量声明,即变量,属性值为undefined。

      而这个创建过程最重要的概念就是提升:

      而如下图所示的代码执行,变量对象的变化过程是怎样的呢?

      那函数内的三个console分别会输出什么呢?
      因为在变量对象的创建过程中,是arguments=>函数声明=>变量声明的过程。在第一个console之前function foo()已经被提升,因此第一次输出的该函数,而第二个console之前bar被提升,并赋值为undefined,因此第二次输出的是undefined。而第三个console之前foo被重新赋值,因此第三个console是"hello"。

      总结起来,变量对象和活动对象其实是同一个对象,他们只是在执行上下文的不同阶段的状态而已。

      下面的截图即是两个阶段的变化。其实变量对象和活动对象是同一个对象,他们只是执行上下文在不同阶段的不同表现形式。在执行阶段变量对象V0会变成活动对象A0。内部的一些引用也会发生变化。

      而如下图所示的代码执行,分别会输出什么呢?

      首先,第一段代码。函数声明首先会被提升第一个console输出hello world。但是后面的hello会被覆盖,第二个console输出hello

      第二段代码。函数声明首先会被提升,但是紧接着会被变量赋值覆盖。因此,两个console输出hello。
总结起来,全局上下文的整个过程即下图所示

      那结合作用域即全局上下文呢,我们一开始的代码代码具体的图解就是下面这张图了。

三 闭包相关
1 闭包分析

      此时,当我们修改inner函数,返回上级作用域的outerName属性时,闭包就产生了。

      这里为什么会产生闭包呢?具体可以参考下方的图示。
      前面的全局入栈和outer函数入栈还是跟原来一样,但是当我们的outer函数入栈执行完毕准备出栈,准备被回收的时候,由于outName还被inner的作用域引用,不能被回收,产生了闭包。

      即所谓的闭包就是通过函数调用,外部持有函数的句柄,让函数的空间不能消失。产生的这块独体的空间永远存在,这块内存对外也是封闭的。所以就叫闭包。

2 常见问题分析

      相信大家在面试的时候会经常问到这样的面试题。下面这段代码输入的是什么呢?

      这里输出的是5个6。需要解释这个问题呢,要涉及到js的的执行环境及作用域链了。

      js的执行环境:JS是单线程环境,即代码的执行是从上到下,依次执行。这样的执行称为同步执行。因为种种不要浪费和节约的原因。JS中引进了异步的机制。这块具体的执行逻辑可以参考https://segmentfault.com/a/11...。在这里,for循环是同步代码,会先从上到下执行。而setTimeout中的是异步代码会将其插入到任务队列当中等待。因此在setTimeout执行的时候,for循环已经执行完成,i已经变成6。作用域链。当setTimeout执行的时候,会向上去查找i的值。往上查找,即for所在的作用域,已经是6了。因此6次setTimeout都会输出6。

      那可能面试官会继续问,我们怎样才能依次输出1-5呢?这里就可以用到闭包来解决了。

      我们将i作为参数传递,并且形成了一个新的立即执行函数作用域。当setTimeout执行的时候,去查找i。即在立即执行函数作用域查找,此时的i我们可以根据上面一部分的分析,形成了闭包之后,它的内存是不会消失的。因此这每次循环的时候都是当前i即1-5。

3 闭包的查看

      其实,我们在chrome的控制台是可以去查看闭包的。在浏览器断点调试,可以去观察下面两幅图的红色圈区别。第二副图可以看到closure,i值是1。依次执行,可以看到i从1到5的变化。

 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104735.html

相关文章

  • 图解JS闭包形成的原因

    摘要:闭包的出现正好结合了全局变量和局部变量的优点。这就是闭包的一个使用场景保存现场。 前言 什么是闭包,其实闭包是可以重用一个对象,又保护对象不被篡改的一种机制。什么是重用一个对象又保护其不被篡改呢?请看下面的详解。 作用域和作用域链 注意理解作用域和作用域链对理解闭包有非常大的帮助,所以我们先说一下作用域和作用域链 什么是作用域作用域表示的是一个变量的可用范围、其实它是一个保存变量的对象...

    wind3110991 评论0 收藏0
  • 前端基础进阶(四):详细图解作用链与闭包

    摘要:之前一篇文章我们详细说明了变量对象,而这里,我们将详细说明作用域链。而的作用域链,则同时包含了这三个变量对象,所以的执行上下文可如下表示。下图展示了闭包的作用域链。其中为当前的函数调用栈,为当前正在被执行的函数的作用域链,为当前的局部变量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初学JavaScrip...

    aikin 评论0 收藏0
  • 【进阶2-1期】深入浅出图解作用链和闭包

    摘要:本期推荐文章从作用域链谈闭包,由于微信不能访问外链,点击阅读原文就可以啦。推荐理由这是一篇译文,深入浅出图解作用域链,一步步深入介绍闭包。作用域链的顶端是全局对象,在全局环境中定义的变量就会绑定到全局对象中。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周开始前端进阶的第二期,本周的主题是作用域闭包,今天是第6天。 本...

    levius 评论0 收藏0
  • 【进阶2-2期】JavaScript深入之从作用链理解闭包

    摘要:使用上一篇文章的例子来说明下自由变量进阶期深入浅出图解作用域链和闭包访问外部的今天是今天是其中既不是参数,也不是局部变量,所以是自由变量。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第7天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计...

    simpleapples 评论0 收藏0
  • javascript系列--javascript深入浅出图解作用链和闭包

    摘要:变量对象也是有父作用域的。作用域链的顶端是全局对象。当函数被调用的时候,作用域链就会包含多个作用域对象。当函数要访问时,没有找到,于是沿着作用域链向上查找,在的作用域找到了对应的标示符,就会修改的值。 一、概要 对于闭包的定义(红宝书P178):闭包就是指有权访问另外一个函数的作用域中的变量的函数。 关键点: 1、闭包是一个函数 2、能够访问另外一个函数作用域中的变量 二、闭包特性 对...

    Jensen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<