前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS入门难点解析6-作用域链

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

作者头像
love丁酥酥
发布2018-08-27 15:35:22
6290
发布2018-08-27 15:35:22
举报
文章被收录于专栏:coding for lovecoding for love

(注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]属性是所有父变量的层级链,就导致了闭包的存在。如下所示:

代码语言:javascript
复制
var a = 10;
function foo() {
    alert(a);
}
  
(function () {
    var a = 20;
    foo(); // 10,这里会访问foo中的[[scope]]的VO中的a
})();

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

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

举个例子:

代码语言:javascript
复制
function foo() {
    function bar() {
        ...
    }
}

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

代码语言:javascript
复制
foo.[[scope]] = [
    globalContext.VO
];

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

4. 函数激活

当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。

这时候执行上下文的作用域链,我们命名为 Scope:

代码语言:javascript
复制
Scope = [AO].concat([scope]]);

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

5. 实例讲解

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

代码语言:javascript
复制
var scope = 'global scope';
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();

执行过程如下:

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

代码语言:javascript
复制
checkscope.[[scope]] = [
    globalContext.VO
];

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

代码语言:javascript
复制
ECStack = [
    checkscopeContext,
    globalContext
];

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

代码语言:javascript
复制
checkscopeContext = {
    Scope: checkscope.[[scope]],
}

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

代码语言:javascript
复制
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: checkscope.[[scope]],
}

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

代码语言:javascript
复制
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}

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

代码语言:javascript
复制
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}

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

代码语言:javascript
复制
ECStack = [
    globalContext
];

参考

JavaScript深入之作用域链

前端基础进阶(四):详细图解作用域链与闭包

JS入门难点解析5-变量对象

javascript中的[[scope]],scope chain,execution context!

js 中的活动对象 与 变量对象 什么区别?

BOOK-《JavaScript高级程序设计(第3版)》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.01.29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 简介
  • 2. 作用域链
  • 3. [scope]与函数创建
  • 4. 函数激活
  • 5. 实例讲解
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档