定义 作用域链(Scope Chain)

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

在词法作用域时讲过,函数在词法阶段(函数定义时)会创建该函数的词法作用域,保存在自身函数的[[Scope]]属性中,且不能被改变。

[[Scope]] 会保存所有父变量对象到其中,就是所有父变量对象的层级链。

Scope = AO + [[Scope]] Scope = [AO].concat([[Scope]]) // 一个执行上下文的完整作用域

  1. function fn() {
  2. function foo() {
  3. // ...
  4. }
  5. }

函数在创建时的作用域为:

  1. fn.[[Scope]] = [
  2. globalContext.VO
  3. ]
  4. foo.[[Scope]] = [
  5. fnContext.AO,
  6. globalContext.VO
  7. ]

在前面说到变量对象在创建阶段还会建立作用域,那么就来分析一下建立作用域的过程。

在创建变量对象 VO 时,会建立属性 Scope,其值是在词法阶段创建的词法作用域 [[Scope]]
这时会复制 [[Scope]] 对象到属性 Scope 中,如下

  1. VO = {
  2. arguments: {...},
  3. Scope: [[Scope]]
  4. }

又将活动对象 VO 添加到作用链的前端,Scope = [AO].concat([[Scope]]) 得到如下

  1. VO = {
  2. arguments: {...},
  3. Scope: [AO, [[Scope]] ]
  4. }

最后再来看一个完整的示例

  1. var a = 10
  2. function fn() {
  3. var b = 20
  4. return b
  5. }
  6. fn()

执行过程如下

  1. 创建函数 fn,同时创建词法作用域 [[Scope]]
  1. fn.[[Scope]] = {
  2. globalContext.VO
  3. }
  1. 执行函数 fn,创建该函数的执行上下文的变量对象,并初始化 arguments和标识符
  1. fnContext = {}
  2. fnContext.VO = {
  3. arguments: {
  4. length: 0
  5. },
  6. b: undefined
  7. }
  1. 建立该变量对象的作用域
  1. fnContext = {
  2. VO: arguments: {
  3. length: 0
  4. },
  5. b: undefined,
  6. Scope: fn.[[scope]]
  7. }
  1. 将活动对象压入 fn 作用域链顶端
  1. fnContext = {
  2. VO: arguments: {
  3. length: 0
  4. },
  5. b: undefined,
  6. Scope: [ VO, [[scope]] ]
  7. }
  1. 开始执行 fn 函数,修改 AO 的值
  1. fnContext = {
  2. VO: arguments: {
  3. length: 0
  4. },
  5. b: 20,
  6. Scope: [ VO, [[scope]] ]
  7. }
  1. 执行完 fn 函数,函数上下文 fnContext 从 ESC 推出。
  1. ESC = [
  2. globalContext
  3. ]