资讯专栏INFORMATION COLUMN

js深入(三)作用域链与闭包

blair / 2430人阅读

摘要:在之前我们根绝对象的原型说过了的原型链,那么同样的万物皆对象,函数也同样存在这么一个链式的关系,就是函数的作用域链作用域链首先先来回顾一下之前讲到的原型链的寻找机制,就是实例会先从本身开始找,没有的话会一级一级的网上翻,直到顶端没有就会报一

在之前我们根绝对象的原型说过了js的原型链,那么同样的js 万物皆对象,函数也同样存在这么一个链式的关系,就是函数的作用域链

作用域链

首先先来回顾一下之前讲到的原型链的寻找机制,就是实例会先从本身开始找,没有的话会一级一级的网上翻,直到顶端没有就会报一个undefined

同样的js的机制就是这样的,函数在执行的时候会先函数本身的上下文的变量对象中查找,没有的话,也会从这个函数被创建的时候的父级的执行上下文的变量对象中去找(词法环境),一直找到全局上下文的变量对象(比如客户端的window对象),这个多层的执行上下文的链式关系就是函数的作用域链

盗一张图

作用域被创建的时机

大家可以看到,我在控制台声明了一个函数,并且打印了他,这个a函数的里边有一个[[scope]]属性,
这是一个内部属性,当一个函数被创建的时候,会保存所有的父级的变量对象(词法环境)到这个里边,比如说上图中 就有一个global 属性展开后,往下找你会发现很多我们常见的属性和方法,比如alert等等

如图

函数作用域的生命周期

姑且叫他生命周期,我是这么理解的,当进入一个函数的上下文,经历了创建阶段之后,就会把函数的作用域链创建出来,直到销毁这个上下文,这个作用域链也是存在的

先来一个正经的例子

function a(){
    var aaa = "aaa";
    return aaa;
}
checkscope();

这个函数的生命周期是这样的

首先函数被创建,先把函数的作用域链保存到函数的[[scope]]属性上边

a.[[scope]] = [
    globalContext.VO//这个也就是我们上边图片里边看的golbal
];
globalContext  全局上下文  VO 这个之前没有介绍 是Variable object的简称,也就是之前经常提到的变量对象
还有一个AO ,这个AO指的是函数被激活的时候(被执行)得活动对象

创建完成之后,执行到a函数,创建了a函数得执行上下文,并压入执行栈里边

现在执行栈里边已经有了两个执行上下文一个globalContext还有一个aContext

到了a函数之后,首先会做一些列得准备工作,就是之前讲到得函数得arguments,this等等

首先第一步复制之前得[[scope]]属性,创建作用域链

aContext = {
    Scope: a.[[scope]],
}

然后开始初始化活动变量 argments对象 形参,函数声明,变量声明等等

最后把把活动变量也塞到作用域链中去

以上,一个函数得准备工作就算是做完了,然后下一步就是函数得执行阶段

之前讲过,在之后阶段得时候函数会根据代码给之前得活动对象赋值,然后执行里边得代码,直到执行完毕

最后,函数执行完毕,函数得上下文被从上下文栈中弹出销毁

在弹出得最后时候,a函数得结构大概长成这个样子

aContext = {
    AO: {
        arguments: {
            length: 0
        },
    },
    Scope: [AO, [[Scope]]]
}

接下来我们在举一个不正经得例子,就是为了证明一下作用域链即使在函数被销毁后,也会存在这么一个事实

闭包

首先什么是闭包,闭包是指在一个函数内部能够访问不是函数得参数,也不是局部变量得函数,所以广义得讲我们用的所有得函数都是可算作是闭包,都能访问全局变量。。。

不过工作中不是这样子得,说正题,给上边得问题举个例子

var item = "1"
function a(){
    var item = "2"
    function b(){
        return item
    }
    return b;
}

var foo = a();
foo();

试着猜想一下这段代码得执行过程

还是来一步一步得解释一下

首先不用多想,进入全局代码,创建全局执行上下文,推入执行栈,全局上下文得一系列初始化

然后创建a , 创建上下文,推入执行栈,一些列得初始化

在执行a得时候创建了b函数,这个时候,还记得上边之前说过得把,作用域链是在被创建得时候确定得

这个时候得b函数得作用域链应该是这个样子的

bContext = {
    Scope: [AO, aContext.AO, globalContext.VO],
}

这个是重点,我们先把执行过程说完

在a函数执行完毕之后,a的上下文栈被弹出

然后在后边执行b函数,然后一样的套路,进上下文压入栈

进栈一些列的初始化

执行完毕b的上下文被弹出

上边已经把顺序说的很清楚了对吧, 执行过程是a进栈出栈,b进栈出栈,但是你打印这段代码的时候
会打印出一个2,就是因为虽然说a的上下文被销毁了,但是b的作用域链里边还是有a的活动对象的
,在b的上下文里边可以找到这个item

这也就是我们之前所说的闭包,也符合闭包的定义

创建他的函数的上下文被销毁,但是他依然存在

在代码中引用了不是自身的参数或者局部变量

最后放一个网上很常见的面试题

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

从作用域链的角度思考一下会打印出什么结果,为什么会打印出这个结果

以上是我对js的作用域链和闭包的一些认识,有不足之处,希望批评指正

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

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

相关文章

  • 前端基础进阶(六):在chrome开发者工具中观察函数调用栈、作用链与闭包

    摘要:在的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,等关键信息的变化。其中表示当前的局部变量对象,表示当前作用域链中的闭包。 showImg(https://segmentfault.com/img/remote/1460000008404321); 在前端开发中,有一个非常重要的技能,叫做断点调试。 在chrome...

    draveness 评论0 收藏0
  • “动静结合” 小白初探静态(词法)作用域,作用链与执行环境(EC)

    摘要:图片中的作用域链,是全局执行环境中的作用域链。然后此活动对象被推入作用域链的最前端。在最后调用的时候,创建先构建作用域链,再创建执行环境,再创建执行环境的时候发现了一个变量标识符。 从图书馆翻过各种JS的书之后,对作用域/执行环境/闭包这些概念有了一个比较清晰的认识。 栗子说明一切 第一个栗子 来看一个来自ECMA-262的栗子: var x = 10; (function foo(...

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

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

    aikin 评论0 收藏0
  • JS基础-作用域、作用链与闭包 Part three

    摘要:作用域执行上下文变量提前函数声明提前确定值范围一段或者一个函数都会生成一个执行上下文全局一段变量定义函数声明函数变量定义函数声明参数集合变量提前代码解析执行过程变量定义提前赋值函数声明提前代码解析函数声明函数表达式执行过程执行过程执行时才能 1.作用域 执行上下文 (变量提前、函数声明提前、确定this值、arguments) 范围:一段或者一个函数(都会生成一个执行上下文) ...

    heartFollower 评论0 收藏0
  • Javascript 函数、作用链与闭包

    摘要:而外层的函数不能访问内层的变量或函数,这样的层层嵌套就形成了作用域链。闭包闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。 闭包是js中一个极为NB的武器,但也不折不扣的成了初学者的难点。因为学好闭包就要学好作用域,正确理解作用域链,然而想做到这一点就要深入的理解函数,所以我们从函数说起。 函数...

    ssshooter 评论0 收藏0

发表评论

0条评论

blair

|高级讲师

TA的文章

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