美文网首页
JS入门难点解析6-作用域链

JS入门难点解析6-作用域链

作者: love丁酥酥 | 来源:发表于2018-01-29 19:50 被阅读84次

(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!)
(注2:更多内容请查看我的目录。)

1. 简介

JS入门难点解析5-变量对象中提到,对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this
    这篇文章主要讲解作用域链。

2. 作用域链

来看《JavaScript高级程序设计》里对作用域链的一段解释:

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(active object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量来自下一个包含环境。这样,一直延续到全局执行环境;全局环境的变量对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。

就是说,作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

3. [[scope]]与函数创建

函数的[[scope]]属性是所有父变量对象的层级链,在函数创建时(函数生命周期分为函数创建和函数调用阶段)存于其中。函数能访问更高一层上下文的变量对象,这种机制是通过函数内部的[[scope]]属性来实现的。(函数创建是在进入执行上下文阶段还是代码执行阶段呢?)

注意重要的一点——[[scope]]在函数创建时被存储——静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。由于是静态存储,再配合上内部函数的[[scope]]属性是所有父变量的层级链,就导致了闭包的存在。如下所示:

var a = 10;
function foo() {
    alert(a);
}
  
(function () {
    var a = 20;
    foo(); // 10,这里会访问foo中的[[scope]]的VO中的a
})();

这个例子也清晰的表明,一个函数(这个例子中为从函数“foo”返回的匿名函数)的[[scope]]持续存在,即使是在函数创建的作用域已经完成之后。

这也就是前面我们所说,函数的作用域在函数定义的时候就决定了。这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

举个例子:

function foo() {
    function bar() {
        ...
    }
}

函数创建时,各自的[[scope]]为:

foo.[[scope]] = [
    globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

4. 函数激活

当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
这时候执行上下文的作用域链,我们命名为 Scope:

Scope = [AO].concat([scope]]);

至此,作用域链创建完毕。

5. 实例讲解

以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:

var scope = 'global scope';
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();

执行过程如下:

1.checkscope 函数被创建,保存作用域链到内部属性[[scope]]

checkscope.[[scope]] = [
    globalContext.VO
];

2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈

ECStack = [
    checkscopeContext,
    globalContext
];

3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链

checkscopeContext = {
    Scope: checkscope.[[scope]],
}

4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: checkscope.[[scope]],
}

5.第三步:将活动对象压入 checkscope 作用域链顶端

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}

6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}

7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出

ECStack = [
    globalContext
];

参考

JavaScript深入之作用域链
前端基础进阶(四):详细图解作用域链与闭包
JS入门难点解析5-变量对象
javascript中的[[scope]],scope chain,execution context!
js 中的活动对象 与 变量对象 什么区别?
BOOK-《JavaScript高级程序设计(第3版)》

相关文章

  • JS进阶系列

    在JS入门难点解析系列中,我们对JS的一些重要概念,比如:作用域,作用域链,原型,原型链,继承,活动对象,this...

  • JS入门难点解析6-作用域链

    (注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!)(注2:更多内容请查看我的目录。) ...

  • 作用域链以及闭包理解

    前言听说作用域链以及闭包是JS面向对象的难点,今天算是初级入门了,了解到点皮毛,学无止境,在技术行业更是!以下是我...

  • JS高级-闭包、沙箱

    作用域,作用域链,预解析 变量:局部变量、全局变量 作用域:变量的使用范围 js中没有块级作用域,一对括号中定义的...

  • JS入门难点解析3-作用域

    (注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!)(注2:更多内容请查看我的目录。) ...

  • 干货!月薪80k前端大佬面试笔记:JS闭包解析!

    三点注意事项 JS没有块级作用域,只有全局作用域和局部作用域(函数作用域)。 JS中的作用域链,内部的作用域可以访...

  • JS基础:作用域

    一、作用域概念-预解析规则、表达式 作用域: 域:空间,范围,区域……作用:读,写 浏览器——JS解析器: 在浏览...

  • JS 作用域链、导入导出

    1. JS 的作用域链 作用域在 JS 中表示变量的可访问性和可见性。JS 作用域有 3 种:1. 全局作用域;2...

  • 闭包

    一、理解闭包前js基础1、作用域链(作用域、作用域链中有说)。2、js的内存回收机制。一个函数在执行开始的时候,会...

  • 作用域和作用链

    关键词:作用域作用链 作用域 js中没有块级作用域 全局作用域,函数作用域太简单,就不演示(≧▽≦)/啦啦啦 作用...

网友评论

      本文标题:JS入门难点解析6-作用域链

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