Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jtwang7 opened this issue May 9, 2021 · 0 comments
Open

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

jtwang7 opened this issue May 9, 2021 · 0 comments

Comments

@jtwang7
Copy link
Owner

jtwang7 commented May 9, 2021

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]]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant