
作用域是個(gè)語(yǔ)言無(wú)關(guān)的概念,你要接觸過(guò)Lisp或者Scheme等語(yǔ)言,應(yīng)該對(duì)這個(gè)概念會(huì)非常熟悉。我在這篇文章會(huì)介紹詞法作用域和動(dòng)態(tài)作用域的基本知識(shí),讓你剛好對(duì)這個(gè)概念了解,此外,我們還會(huì)討論下JavaScript的詞法作用域。
首先你得明白程序設(shè)計(jì)中作用域這個(gè)概念:通常來(lái)說(shuō),一段程序代碼中所用到的名字并不總是有效/可用的,而限定這個(gè)名字的可用性的代碼范圍就是這個(gè)名字的作用域。
詞法作用域,也叫靜態(tài)作用域,它的作用域是指在詞法分析階段就確定了,不會(huì)改變。動(dòng)態(tài)作用域是在運(yùn)行時(shí)根據(jù)程序的流程信息來(lái)動(dòng)態(tài)確定的,而不是在寫(xiě)代碼時(shí)進(jìn)行靜態(tài)確定的。
我們以下面這段Javascript代碼來(lái)說(shuō)明詞法作用域和動(dòng)態(tài)作用域,但是你要明白,闡述的概念是與Javascript無(wú)關(guān)的。
var a = 2;
function foo() {
console.log(a); // 會(huì)輸出2還是3?
}
function bar() {
var a = 3;
foo();
}
bar();
如果是詞法作用域,它會(huì)讓 foo() 函數(shù)引用到全局作用域中的 a,因此會(huì)輸出 2。我們說(shuō)過(guò),詞法作用域是寫(xiě)代碼的時(shí)候就靜態(tài)確定下來(lái)的。Javascript中的作用域就是詞法作用域(事實(shí)上大部分語(yǔ)言都是基于詞法作用域的),所以這段代碼在瀏覽器中運(yùn)行的結(jié)果是輸出 2。

而動(dòng)態(tài)作用域并不關(guān)心函數(shù)和作用域是如何聲明以及在何處聲明的,只關(guān)心它們從何處調(diào)用。換句話說(shuō),作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中的作用域嵌套。因此,如果Javascript具有動(dòng)態(tài)作用域,理論上輸出結(jié)果是 3。
為什么會(huì)這樣?因?yàn)楫?dāng) foo() 無(wú)法找到 a 的變量引用時(shí),會(huì)順著調(diào)用棧在調(diào)用 foo() 的地方查找 a ,而不是在嵌套的詞法作用域鏈中向上查找。由于 foo() 是在 bar() 中調(diào)用的,引擎會(huì)檢查 bar() 的作用域,并在其中找到值為 3 的變量 a。
你可能會(huì)覺(jué)得很奇怪,但這其實(shí)是因?yàn)槟憧赡苤粚?xiě)過(guò)基于詞法作用域的代碼(或者至少以詞法作用域?yàn)榛A(chǔ)進(jìn)行了深入的思考),因此對(duì)動(dòng)態(tài)作用域感到陌生。如果你只用基于動(dòng)態(tài)作用域的語(yǔ)言寫(xiě)過(guò)代碼,就會(huì)覺(jué)得這是很自然的,而詞法作用域看上去才怪怪的。
需要明確的是,Javascript并不具有動(dòng)態(tài)作用域,它只有詞法作用域,簡(jiǎn)單明了。但是,它的 eval()、with、this機(jī)制某種程度上很像動(dòng)態(tài)作用域,使用上要特別注意。
主要區(qū)別:詞法作用域是在寫(xiě)代碼或者定義時(shí)確定的,而動(dòng)態(tài)作用域是在運(yùn)行時(shí)確定的(this也是?。?。詞法作用域關(guān)注函數(shù)在何處聲明,而動(dòng)態(tài)作用域關(guān)注函數(shù)從何處調(diào)用。
參考資料
- 書(shū)籍:《你不知道的JavaScript:上卷》
- 百度百科詞條:作用域
- cnblogs:《淺談靜態(tài)作用域和動(dòng)態(tài)作用域》