作用域链

温馨提示: 阅读本文需要熟悉:

  • 执行上下文
  • 变量对象

作用域链定义了查找变量的规则,其本质是所有执行上下文变量对象列表。作用域链与函数的联系密不可分,当函数被创建的时候,会被添加一个[[SCOPE]]属性(内部属性),它保存着上层所有执行上下文变量对象,且由于该属性是在函数被创建时被添加的,所以它的特点是:

  • 静态
  • 不可变

静态可以理解成[[SCOPE]]的值与函数书写的位置有关。而不可变的意思是,不论你是用什么方式调用函数,[[SCOPE]]属性都不会改变,且就算函数创建之后从未被调用,该属性仍一直存在,直到函数被销毁!我们可以结合代码深入理解[[SCOPE]]:

1
2
3
4
5
6
7
8
9
function foo() {
  var a = 20;
  function bar() {
    console.log(a);
  }
  bar();
}
 
foo(); // 20

foo函数被创建时,[[SCOPE]]被赋值。由于foo函数书写位置的外部不存在任何函数(全局代码),所以它的[[SCOPE]]值只有全局执行上下文的变量对象(全局对象)。这里需要说明一点,[[SCOPE]]的真正实现可能并不是数组,我们这里使用数组只是因为相对更加熟悉。

foo.[[SCOPE]] = [
  // 全局上下文的变量对象
  globalConext.VO,
]

代码继续执行,foo函数被调用,执行foo函数体内的代码,执行到bar函数被创建时,由于创建bar函数的代码位于foo函数的代码块内部,所以bar函数[[SCOPE]]属性变成了这样:

bar.[[SCOPE]] = [
  // foo函数被调用的执行上下文的活跃对象
  fooConext.AO,
  // 全局上下文的变量对象
  globalContext.VO,
]

解释完[[SCOPE]]以后,还需要介绍另外一个特性 👉 SCOPE。别看他们很像,本质上有很大的区别。上面我们说过,[[SCOPE]]是函数的属性,而SCOPE执行上下文的属性。一言以蔽之,SCOPE也是一个特殊的对象列表,由当前的活跃对象和激活该执行上下文的函数的[[SCOPE]]组成。

activeExecutionContext = {
    VO: {...}, // 或者 AO
    Scope: [ 
      // 当前的活跃对象
      AO,
      // 激活该执行上下文的函数的[[SCOPE]]
      [[Scope]]
    ]
};

我们还是拿一段代码来说明SCOPE吧,请看下面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var x = 10;
 
function foo() {
 
  var y = 20;
 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  }
 
  bar();
}
 
foo(); // 60

首先,代码开始执行,全局上下文的变量对象如下所示:

globalContext.VO === Global = {
  x: 10
  foo: 函数地址值
};

代码继续执行,foo函数被创建,其[[Scope]]属性如下所示:

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

接着,foo函数被调用,它的执行上下文的活跃对象如下所示:

fooContext.AO = {
  y: 20,
  bar: 
};

同时,foo函数的执行上下文的作用域链如下所示:

fooContext.Scope = fooContext.AO + foo.[[Scope]] 
 
// foo.[[Scope]]我们在上面已经指出是globalContext.VO,所以:
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];

继续执行foo函数体内代码,函数bar被创建,其[[Scope]]属性如下所示:

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

紧接着函数bar被调用,其对应的活跃对象如下所示:

barContext.AO = {
  z: 30
};

同时,函数bar的执行上下文的作用域链如下所示:

barContext.Scope = barContext.AO + bar.[[Scope]]
 
barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

如下是“x”,“y”和“z”标识符的查询过程:

- "x"

// 首先在barContext.AO中寻找,没有找到

-- barContext.AO // not found

// 然后在fooContext.AO继续找,还是没有找到哦啊

-- fooContext.AO // not found

// 终于在全局执行上下文中找到了!

-- globalContext.VO // found - 10

- "y"
  
-- barContext.AO // not found

-- fooContext.AO // found - 20

- "z"
  
-- barContext.AO // found - 30

以上就是作用域链的简单介绍~

updatedupdated2020-06-212020-06-21