# 附錄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與對象原型*”。