引言
???????作用域與作用域鏈?zhǔn)荍S應(yīng)用中無時無刻不在影響程序運(yùn)行的關(guān)鍵屬性,但是由于它的不可見性,或者說它存在的過于普遍粥鞋,簡直就像空氣一樣。所以對它的談及瞄崇,都很簡單呻粹,而理解起來也不復(fù)雜。
???????但是由于它的重要性苏研,對它做一個篇幅的說明等浊,也是一件理所應(yīng)當(dāng)?shù)氖虑椤6宜鋵?shí)也并沒有理解上那么簡單楣富。
???????本文對作用域及作用域鏈的說明凿掂,可能并不全面。筆者盡量以自身開發(fā)過程中的理解,做到表述全面庄萎。
1踪少、執(zhí)行環(huán)境
???????按照《JavaScript高級程序設(shè)計》第3版中的定義——執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問其他數(shù)據(jù),決定了它們各自的行為糠涛。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象援奢,環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。
???????對于環(huán)境的簡單理解忍捡,可以理解為{}一個大括號就是一個執(zhí)行環(huán)境集漾。如{{}},就是包含關(guān)系的兩個執(zhí)行環(huán)境砸脊。而由于所有的函數(shù)和變量具篇,最終都是通過最外圍有對應(yīng)的調(diào)用,才能最終實(shí)現(xiàn)運(yùn)行凌埂,所以將最外圍的執(zhí)行環(huán)境定義為全局執(zhí)行環(huán)境驱显,在宿主為web瀏覽器時,全局執(zhí)行環(huán)境就為window對象瞳抓。
???????每個函數(shù)都有自己的執(zhí)行環(huán)境埃疫。當(dāng)執(zhí)行程序進(jìn)入一個函數(shù)時,函數(shù)的環(huán)境就會被推入一個環(huán)境棧中孩哑。而在函數(shù)執(zhí)行之后栓霜,棧將環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境横蜒。之后胳蛮,該環(huán)境因?yàn)?strong>執(zhí)行完畢,隨即被銷毀愁铺。但有一個例外鹰霍,全局環(huán)境將永遠(yuǎn)不會被銷毀,直到程序被關(guān)閉茵乱,如關(guān)閉瀏覽器。
由上圖我們清晰的看到JavaScript的兩個階段的執(zhí)行流程孟岛。
2瓶竭、作用域與作用域鏈
???????作用域鏈和作用域是一個包含關(guān)系。當(dāng)代碼在一個環(huán)境中執(zhí)行時渠羞,會創(chuàng)建變量對象的一個作用域鏈斤贰。而作用域鏈中,包含的就是一個個有序排列的作用域次询。作用域鏈的作用荧恍,就是保證執(zhí)行環(huán)境中的變量和函數(shù)的有序訪問。
???????每次的變量或函數(shù)聲明,都會形成一個標(biāo)識符(即變量名或函數(shù)名)送巡。 作用域鏈中的向上查找摹菠,實(shí)際上就是標(biāo)識符的查找。找到匹配的標(biāo)識符骗爆,就調(diào)用它的值次氨。
???????作用域鏈中包含的每個作用域?qū)ο螅渲卸及陨憝h(huán)境中可訪問的變量或函數(shù)(script作用域中摘投,為全部的變量和函數(shù);函數(shù)作用域中煮寡,子函數(shù)調(diào)用父函數(shù)環(huán)境中的變量或函數(shù)會形成閉包,那么可訪問變量就為閉包調(diào)用的變量)犀呼。換一種角度理解的話幸撕,這些環(huán)境中可訪問的變量或函數(shù)就是標(biāo)識符,是當(dāng)前作用域中可通過作用域鏈向上查找的標(biāo)識符外臂。
???????一大段的文字不光讀起來枯燥杈帐,理解起來也困難。所以专钉,stop talking挑童,show code。
以函數(shù)為例
function test() {
let a = 1; //注釋此行跃须,保留其他a站叼,c()打印2,并且作用域鏈?zhǔn)ラ]包對象
let b = 0;
return () => { //形成閉包環(huán)境
console.log(a);
}
}
let a = 2; //注釋此行及test中的a菇民,c()打印3
window.a = 3; //注釋全部a尽楔,報錯
const c = test();
c();
console.dir(test);
console.dir(c);
以上代碼,通過3次注釋以及chrome瀏覽器打印第练,可以清晰的看到作用域與作用域鏈的形成及作用域鏈按順序查找的特性
1)首先是閉包調(diào)用——所有a都不注釋
由上圖可以看出阔馋,此時作用域鏈中存在3個作用域?qū)ο蟆?/strong> 按索引順序?yàn)?strong>0—Closure閉包環(huán)境、1—Script標(biāo)簽環(huán)境娇掏、2—Global全局環(huán)境;
索引順序就是標(biāo)識符的查找順序呕寝,所以當(dāng)執(zhí)行函數(shù)c()時,先在Closure閉包環(huán)境中查找婴梧,然后是Script標(biāo)簽作用域下梢,最后是Global全局作用域。因?yàn)樾纬闪碎]包塞蹭,那么標(biāo)識符的查找肯定能在閉包環(huán)境中找到對應(yīng)結(jié)果孽江,所以打印1;
值得注意的一點(diǎn)是,Closure與Script作用域?qū)ο笾蟹纾鎯Φ臉?biāo)識符的不同岗屏。closure只存儲調(diào)用值a = 1,并沒有存儲b = 0。而Script中將環(huán)境中的所有標(biāo)識符都存在了對象中;
2)然后是取消閉包調(diào)用这刷,改為Script標(biāo)簽作用域查找——注釋test中的a = 1
由上圖可以看出婉烟,此時作用域鏈中的Closure閉包作用域消失,只剩下2個對象0—Script標(biāo)簽環(huán)境崭歧、2—Global全局環(huán)境;
那么同樣按索引順序查找隅很,找到了Script標(biāo)簽環(huán)境中的a = 2,沒有繼續(xù)尋找Global全局環(huán)境中的window.a = 3率碾,最終打印2;
3)最后是Global全局作用域查找——注釋test中的a = 1及script中的a = 2
與2)中情況對比叔营,作用域鏈中的對象沒有改變所宰。但是Script對象中的標(biāo)識符绒尊,隨著a = 2被注釋而消失
按索引順序查找,找到了Script標(biāo)簽環(huán)境中沒有a仔粥,繼續(xù)尋找Global全局環(huán)境婴谱,最終找到window.a = 3,打印3;
函數(shù)test的打印躯泰,從始至終都是Script標(biāo)簽作用域及Global全局作用域谭羔。理解了c函數(shù)的作用域鏈,自然就明白了函數(shù)test的麦向,所以不再展開贅述瘟裸。
如有錯誤或闡述不充分之處,歡迎指正~