附录A:动态作用域
最后更新于:2022-04-02 02:03:02
# 附录A:动态作用域
在第二章中,作为与 JavaScript 中(事实上,其他大多数语言也是)作用域的工作方式模型 —— “词法作用域”的对比,我们谈到了“动态作用域”。
我们将简单地检视动态作用域,来彻底说明这种比较。但更重要的是,对于 JavaScript 中的另一种机制(`this`)来说动态作用域实际上是它的一个近亲表兄,我们将在本系列的“*this与对象原型*”中详细讲解这种机制。
正如我们在第二章中看到的,词法作用域是一组关于 *引擎* 如何查询变量和它在何处能够找到变量的规则。词法作用域的关键性质是,它是在代码编写时被定义的(假定你不使用 `eval()` 或 `with` 作弊的话)。
动态作用域看起来在暗示,有充分的理由,存在这样一种模型,它的作用域是在运行时被确定的,而不是在编写时静态地确定的。让我们通过代码来说明这样的实际情况:
```source-js
function foo() {
console.log( a ); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
```
在 `foo()` 的词法作用域中指向 `a` 的 RHS 引用将被解析为全局变量 `a`,它将导致输出结果为值 `2`。
相比之下,动态作用域本身不关心函数和作用域是在哪里和如何被声明的,而是关心 它们是从何处被调用的。换句话说,它的作用域链条是基于调用栈的,而不是代码中作用域的嵌套。
所以,如果 JavaScript 拥有动态作用域,当 `foo()` 被执行时,理论上 下面的代码将得出 `3` 作为输出结果。
```source-js
function foo() {
console.log( a ); // 3 (不是 2!)
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
```
这怎么可能?因为当 `foo()` 不能为 `a` 解析出一个变量引用时,它不会沿着嵌套的(词法)作用域链向上走一层,而是沿着调用栈向上走,以找到 `foo()` 是 *从何处* 被调用的。因为 `foo()` 是从 `bar()` 中被调用的,它就会在 `bar()` 的作用域中检查变量,并且在这里找到持有值 `3` 的 `a`。
奇怪吗?此时此刻你可能会这样认为。
但这可能只是因为你仅在拥有词法作用域的代码中工作过。所以动态作用域看起来陌生。如果你仅使用动态作用域的语言编写过代码,它看起来就是很自然的,而词法作用域将是个怪东西。
要清楚,JavaScript 实际上没有动态作用域。它拥有词法作用域。就这么简单。但是 `this` 机制有些像动态作用域。
关键的差异:词法作用域是编写时的,而动态作用域(和 `this`)是运行时的。词法作用域关心的是 *函数在何处被声明*,但是动态作用域关心的是函数 *从何处* 被调用。
最后:`this` 关心的是 *函数是如何被调用的*,这揭示了 `this` 机制与动态作用域的想法有多么紧密的关联。要了解更多关于 `this` 的细节,请阅读 “*this与对象原型*”。
';