大多數(shù)時(shí)候,我們對(duì)作用域產(chǎn)生混亂的主要原因是分不清楚應(yīng)該按照函數(shù)位置的嵌套順序,還是按照函數(shù)的調(diào)用順序進(jìn)行變量查找。再加上this機(jī)制的干擾,使得變量查找極易出錯(cuò)。這實(shí)際上是由兩種作用域工作模型導(dǎo)致的,作用域分為詞法作用域和動(dòng)態(tài)作用域,分清這兩種作用域模型就能夠?qū)ψ兞坎檎疫^(guò)程有清晰的認(rèn)識(shí)。本文是深入理解javascript作用域系列第二篇——詞法作用域和動(dòng)態(tài)作用域
第一篇介紹過(guò),編譯器的第一個(gè)工作階段叫作分詞,就是把由字符組成的字符串分解成詞法單元。這個(gè)概念是理解詞法作用域的基礎(chǔ)
簡(jiǎn)單地說(shuō),詞法作用域就是定義在詞法階段的作用域,是由寫代碼時(shí)將變量和塊作用域?qū)懺谀睦飦?lái)決定的,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變
關(guān)系
無(wú)論函數(shù)在哪里被調(diào)用,也無(wú)論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時(shí)所處的位置決定
function foo(a) { var b = a * 2; function bar(c) { console.log( a, b, c ); } bar(b * 3);}foo( 2 ); // 2 4 12
在這個(gè)例子中有三個(gè)逐級(jí)嵌套的作用域。為了幫助理解,可以將它們想象成幾個(gè)逐級(jí)包含的氣泡
作用域氣泡由其對(duì)應(yīng)的作用域塊代碼寫在哪里決定,它們是逐級(jí)包含的
氣泡1包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符:foo
氣泡2包含著foo所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符:a、bar和b
氣泡3包含著bar所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符:c
查找
作用域氣泡的結(jié)構(gòu)和互相之間的位置關(guān)系給引擎提供了足夠的位置信息,引擎用這些信息來(lái)查找標(biāo)識(shí)符的位置
在代碼片段中,引擎執(zhí)行console.log(...)聲明,并查找a、b和c三個(gè)變量的引用。它首先從最內(nèi)部的作用域,也就是bar(...)函數(shù)的作用域開(kāi)始查找。引擎無(wú)法在這里找到a,因此會(huì)去上一級(jí)到所嵌套的foo(...)的作用域中繼續(xù)查找。在這里找到了a,因此引擎使用了這個(gè)引用。對(duì)b來(lái)講也一樣。而對(duì)c來(lái)說(shuō),引擎在bar(...)中找到了它
[注意]詞法作用域查找只會(huì)查找一級(jí)標(biāo)識(shí)符,如果代碼引用了foo.bar.baz,詞法作用域查找只會(huì)試圖查找foo標(biāo)識(shí)符,找到這個(gè)變量后,對(duì)象屬性訪問(wèn)規(guī)則分別接管對(duì)bar和baz屬性的訪問(wèn)
遮蔽
作用域查找從運(yùn)行時(shí)所處的最內(nèi)部作用域開(kāi)始,逐級(jí)向外或者說(shuō)向上進(jìn)行,直到遇見(jiàn)第一個(gè)匹配的標(biāo)識(shí)符為止
在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符,這叫作“遮蔽效應(yīng)”,內(nèi)部的標(biāo)識(shí)符“遮蔽”了外部的標(biāo)識(shí)符
全局變量會(huì)自動(dòng)為全局對(duì)象的屬性,因此可以不直接通過(guò)全局對(duì)象的詞法名稱,而是間接地通過(guò)對(duì)全局對(duì)象屬性的引用來(lái)對(duì)其進(jìn)行訪問(wèn)
通過(guò)這種技術(shù)可以訪問(wèn)那些被同名變量所遮蔽的全局變量。但非全局的變量如果被遮蔽了,無(wú)論如何都無(wú)法被訪問(wèn)到
javascript使用的是詞法作用域,它的最重要的特征是它的定義過(guò)程發(fā)生在代碼的書(shū)寫階段
那為什么要介紹動(dòng)態(tài)作用域呢?實(shí)際上動(dòng)態(tài)作用域是javascript另一個(gè)重要機(jī)制this的表親。作用域混亂多數(shù)是因?yàn)樵~法作用域和this機(jī)制相混淆,傻傻分不清楚
動(dòng)態(tài)作用域并不關(guān)心函數(shù)和作用域是如何聲明以及在任何處聲明的,只關(guān)心它們從何處調(diào)用。換句話說(shuō),作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中的作用域嵌套
【1】如果處于詞法作用域,也就是現(xiàn)在的javascript環(huán)境。變量a首先在foo()函數(shù)中查找,沒(méi)有找到。于是順著作用域鏈到全局作用域中查找,找到并賦值為2。所以控制臺(tái)輸出2
【2】如果處于動(dòng)態(tài)作用域,同樣地,變量a首先在foo()中查找,沒(méi)有找到。這里會(huì)順著調(diào)用棧在調(diào)用foo()函數(shù)的地方,也就是bar()函數(shù)中查找,找到并賦值為3。所以控制臺(tái)輸出3
兩種作用域的區(qū)別,簡(jiǎn)而言之,詞法作用域是在定義時(shí)確定的,而動(dòng)態(tài)作用域是在運(yùn)行時(shí)確定的
聯(lián)系客服