感謝社區中各位的大力支持,譯者再次奉上一點點福利:阿里云產品券,享受所有官網優惠,并抽取幸運大獎:點擊這里領取
在第二章中,作為與JavaScript中(事實上,其他大多數語言也是)作用域的工作方式模型 —— “詞法作用域”的對比,我們談到了“動態作用域”。
我們將簡單地檢視動態作用域,來徹底說明這種比較。但更重要的是,對于JavaScript中的另一種機制(this
)來說動態作用域實際上是它的一個近親表兄,我們將在本系列的“this與對象原型”中詳細講解這種機制。
正如我們在第二章中看到的,詞法作用域是一組關于 引擎 如何查詢變量和它在何處能夠找到變量的規則。詞法作用域的關鍵性質是,它是在代碼編寫時被定義的(假定你不使用eval()
或with
作弊的話)。
動態作用域看起來在暗示,有充分的理由,存在這樣一種模型,它的作用域是在運行時被確定的,而不是在編寫時靜態地確定的。讓我們通過代碼來說明這樣的實際情況:
function foo() {
console.log( a ); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
在foo()
的詞法作用域中指向a
的RHS引用將被解析為全局變量a
,它將導致輸出結果為值2
。
相比之下,動態作用域本身不關心函數和作用域是在哪里和如何被聲明的,而是關心 它們是從何處被調用的。換句話說,它的作用域鏈條是基于調用棧的,而不是代碼中作用域的嵌套。
所以,如果JavaScript擁有動態作用域,當foo()
被執行時,理論上 下面的代碼將得出3
作為輸出結果。
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與對象原型”。