Skip to content

JS深入浅出 - 作用域链 #5

Open
@jtwang7

Description

@jtwang7

JS深入浅出 - 作用域链

参考文章:
JavaScript深入之作用域链
你不知道的JavaScript】(三)执行上下文及其生命周期
JavaScript深入之词法作用域和动态作用域

1. 概念

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的指针链表就叫做作用域链。

作用域链保存的是指向执行上下文变量对象引用地址的指针,而不是保存变量对象的具体地址。

2. 作用域

要了解 JS 的作用域链,首先要知道什么是作用域。

  • 作用域是指程序源代码中定义变量的区域,此外它还作为一套规则,规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

作用域按性质分为两种:

  • 词法作用域,又称静态作用域。
  • 动态作用域。

JavaScript 中采用词法作用域。

作用域在 JS 中又分为三种:

  • 全局作用域
    • 有且只有一个。
    • 定义在全局作用域中的变量可在代码任何地方被访问(与作用域链有关)。
  • 函数作用域
    • 函数定义时确定的变量区域,其内部定义的变量只能在该函数内被调用。
  • 块级作用域(ES6 新增)
    • 任何代码块内部(一对花括号包裹)都会创建一个块级作用域。
    • 块级作用域中有暂时性死区,变量禁止重复声明等特点。

词法作用域(lexical scoping)

词法作用域特点在于:任何变量和函数的作用域范围在书写代码时就被确定。
而动态作用域需要在变量或函数被调用时才能确定。

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();
  • 上述代码中,若采用词法作用域,最终输出结果为 1
    过程分析:
    首先调用 bar(),执行内部的 foo(),遇到 console.log(value); 语句,由于在 foo() 的块级(词法)作用域中没有找到声明变量,因此沿着作用域链向外查找(查找过程后续细讲),此时 foo() 的外层(词法)作用域为全局作用域,因此 value === 1
  • 若采用动态作用域,最终输出结果为 2
  • 过程分析:
    首先调用 bar(),执行内部的 foo(),遇到 console.log(value); 语句,由于在 foo() 的块级(动态)作用域中没有找到声明变量,因此沿着作用域链向外查找,此时由于 foo() 是在 bar() 作用域中调用的,因此 foo() 的外层(动态)作用域为 bar() 的作用域,因此 value === 2

相比动态作用域,词法作用域更方便开发者查找分析,我们只需要看调用的函数在代码中定义的位置,即可清晰判断各层的作用域。

3. 作用域链的创建

函数定义阶段

函数有一个内部属性 [[scope]](数组),在函数定义时就自动生成,其保存了所有父级及以上层级的变量对象。换句话说,[[scope]] 就是当前函数下所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!因为它没有添加当前执行上下文的变量对象。

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

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

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

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

函数调用阶段

当函数被调用时,JS 引擎开始创建函数执行上下文,该阶段作用域链才真正的生成:
在创建完变量对象后,JS 会将变量对象添加到 [[scope]] 的前端,生成一条作用域链并保存为 Scope

Scope = [VO].concat([[scope]])

至此作用域链创建完毕,当进入函数上下文执行阶段时,VO 会被激活为 AO,此时作用域链通过代码可表示为:

Scope = [AO, ...[[scope]]]

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions