This website requires JavaScript.
ROSY
GRAY

觉宇宙之无穷,识盈虚之有数。

——王勃/《滕王阁序》

BING
转载

JavaScript深入之作用域链

共 3,349 字,需阅读 8 分钟2020/07/07 下午314 次阅读

JavaScript深入系列第五篇,讲述作用链的创建过程,最后结合着变量对象,执行上下文栈,让我们一起捋一捋函数创建和执行的过程中到底发生了什么?

前言

《JavaScript深入之执行上下文栈》 中讲到,当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

今天重点讲讲作用域链。

作用域链

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

下面,让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。

函数创建

《JavaScript深入之词法作用域和动态作用域》 中讲到,函数的作用域在函数定义的时候就决定了。

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

举个例子:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
function foo() { function bar() { ... } }

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

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ];

函数激活

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

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

              
  • 1
  • 2
Scope = [AO].concat([[Scope]]);

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

捋一捋

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

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
var scope = "global scope"; function checkscope(){ var scope2 = 'local scope'; return scope2; } checkscope();

执行过程如下:

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

              
  • 1
  • 2
  • 3
checkscope.[[scope]] = [ globalContext.VO ];

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

              
  • 1
  • 2
  • 3
  • 4
ECStack = [ checkscopeContext, globalContext ];

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

              
  • 1
  • 2
  • 3
checkscopeContext = { Scope: checkscope.[[scope]], }

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

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined } }

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

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] }

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

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]] }

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

              
  • 1
  • 2
  • 3
ECStack = [ globalContext ];

下一篇文章

《JavaScript深入之从ECMAScript规范解读this》

本文相关链接

《JavaScript深入之词法作用域和动态作用域》

《JavaScript深入之执行上下文栈》

《JavaScript深入之变量对象》

深入系列

JavaScript深入系列目录地址: https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

原文链接

JavaScript深入之作用域链

自由转载 - 署名 - 非商业性使用https://blog.rosygray.com/article/6
0 / 0 条看法
访客身份
在下有一拙见,不知...
期待你的捷足先登