鄧緒高
摘要:在JavaScript中,一個方法使用的地方和定義的地方有時會大相徑庭,在該方法執(zhí)行時,它不能訪問哪些變量?可以訪問哪些變量?這個需要怎么判斷呢?這就是該文需要分析的問題——詞法作用域。
關(guān)鍵詞:JavaScript;執(zhí)行環(huán)境;活動對象;詞法作用域;閉包
中圖分類號:TP271 文獻標識碼:A 文章編號:1009-3044(2012)36-8663-04
1 JavaScript詞法作用域
JavaScript中詞法作用域指變量的作用域不是執(zhí)行時決定而是在定義時決定,也就是說通過靜態(tài)分析就能確定,詞法作用域取決于源碼,因此詞法作用域也叫做靜態(tài)作用域。with和eval除外,所以只能說JS的作用域機制非常接近詞法作用域。
我們可以很快的寫出一個方法,但你可能沒有進行過深入的學習和了解到底方法內(nèi)部是如何執(zhí)行,執(zhí)行的細節(jié)又是什么。這就需要了解JavaScript引擎的工作方式,才能了解這些細節(jié),下面我們就把JavaScript引擎對一個方法的解析過程進行一個稍微深入一些的介紹
3 JavaScript方法解析過程
JavaScript是一種解釋型語言,要開始解釋執(zhí)行,得通過詞法分析和語法分析得到語法分析樹后才可以。當一個文檔流中包含多個script代碼段,那么它們的運行順序是:
1)讀入第一個代碼段
2)做詞法分析和語法分析,有錯則報語法錯誤,并跳轉(zhuǎn)到步驟5
3)對var變量和function定義做“預(yù)解析“
4)執(zhí)行代碼段,有錯則報錯
5)如果還有下一個代碼段,則讀入下一個代碼段,重復步驟2
6)結(jié)束
3.1 特殊說明
全局域(window)域下所有JavaScript代碼會被自動執(zhí)行,可以被看成是一個“匿名方法“,而此“匿名方法“內(nèi)的其它方法則是在被顯示調(diào)用的時候才被執(zhí)行。
3.2 關(guān)鍵步驟
上面的過程,我們主要是分成兩個階段
1)解析:就是通過語法分析和預(yù)解析構(gòu)造合法的語法分析樹。
2)執(zhí)行:執(zhí)行具體的某個function,JavaScript引擎在執(zhí)行每個函數(shù)實例時,都會創(chuàng)建一個執(zhí)行環(huán)境(ExecutionContext)和活動對象(activeObject)。
3.3 關(guān)鍵概念
到這里,我們再更強調(diào)以下一些概念,這些概念都會在下面用一個一個的實體來表示,便于大家理解
1)語法分析樹(SyntaxTree)可以直觀地表示出這段代碼的相關(guān)信息,具體的實現(xiàn)就是JavaScript引擎創(chuàng)建了一些表,用來記錄每個方法內(nèi)的變量集(variables),方法集(functions)和作用域(scope)等
2)執(zhí)行環(huán)境(ExecutionContext)可理解為一個記錄當前執(zhí)行的方法(外部描述信息)的對象,記錄所執(zhí)行方法的類型,名稱,參數(shù)和活動對象(activeObject)
3)活動對象(activeObject)可理解為一個記錄當前執(zhí)行的方法(內(nèi)部執(zhí)行信息)的對象,記錄內(nèi)部變量集(variables)、內(nèi)嵌函數(shù)集(functions)、實參(arguments)、作用域鏈(scopeChain)等執(zhí)行所需信息,其中內(nèi)部變量集(variables)、內(nèi)嵌函數(shù)集(functions)是直接從第一步建立的語法分析樹復制過來的
上面就是關(guān)于語法分析樹的一個簡單表示,正如我們前面分析的,語法分析樹主要記錄了每個 function中的變量集(variables),方法集(functions)和作用域(scope)。以下是語法分析樹關(guān)鍵點:
1)變量集(variables)中,只有變量定義,沒有變量值,這時候的變量值全部為“undefined”
2)作用域(scope),根據(jù)詞法作用域的特點,這個時候每個變量的作用域就已經(jīng)明確了,而不會隨執(zhí)行時的環(huán)境而改變。
3)作用域(scope)建立規(guī)則
a) 對于函數(shù)聲明和匿名函數(shù)表達式來說,[scope]就是它創(chuàng)建時的作用域
b) 對于有名字的函數(shù)表達式,[scope]頂端是一個新的JavaScript對象(也就是繼承了Object.prototype),這個對象有兩個屬性,第一個是自身的名稱,第二個是定義的作用域,第一個函數(shù)名稱是為了確保函數(shù)內(nèi)部的代碼可以無誤地訪問自己的函數(shù)名進行遞歸。
4.2 執(zhí)行環(huán)境與活動對象
語法分析完成,開始執(zhí)行代碼。我們調(diào)用每一個方法的時候,JavaScript引擎都會自動為其建立一個執(zhí)行環(huán)境和一個活動對象,它們和方法實例的生命周期保持一致,為方法執(zhí)行提供必要的執(zhí)行支持,針對上面的幾個方法,我們這里統(tǒng)一為其建立了活動對象,具體如下:
上面每一個活動對象都存儲了相應(yīng)方法的內(nèi)部變量集(variables)、內(nèi)嵌函數(shù)集(functions)、形參(parameters)、實參(arguments)等執(zhí)行所需信息?;顒訉ο箨P(guān)鍵點如下:
1) 創(chuàng)建活動對象,從語法分析樹復制方法的內(nèi)部變量集(variables)和內(nèi)嵌函數(shù)集(functions)
2) 方法開始執(zhí)行,活動對象里的內(nèi)部變量集全部被重置為 undefined
3) 創(chuàng)建形參(parameters)和實參(arguments)對象,同名的實參,形參和變量之間是【引用】關(guān)系
4) 執(zhí)行方法內(nèi)的賦值語句,這才會對變量集中的變量進行賦值處理
5) 變量查找規(guī)則是首先在當前執(zhí)行環(huán)境的activeObject中尋找,沒找到,則順著執(zhí)行環(huán)境中屬性scopeChain指向的activeObject中尋找,一直到Global Object(window)
6) 方法執(zhí)行完成后,內(nèi)部變量值不會被重置,至于變量什么時候被銷毀,請參考下面一條
7) 方法內(nèi)變量的生存周期取決于方法實例是否存在活動引用,如沒有就銷毀活動對象
8) 步驟f和g是使閉包能訪問到外部變量的根本原因
5 重釋經(jīng)典案例
在案列一中,由于在一個方法中,同名的實參,形參和變量之間是引用關(guān)系,也就是說JavaScript引擎的處理是同名變量和形參都引用同一個內(nèi)存地址,所以在二中的修改arguments才會有影響到局部變量的這種情況出現(xiàn)。
在案例二中,根據(jù)JavaScript引擎變量查找規(guī)則,在當前執(zhí)行環(huán)境的ActiveObject中尋找,未果,則順著執(zhí)行環(huán)境中屬性ScopeChain指向的ActiveObject中尋找,一直到GlobalObject(window),所以在四中,因為在當前的ActiveObject中找到了有變量i的定義,只是值為“undefined”,所以直接輸出“undefined”了
6 總結(jié)
以上是我在學習和使用了JavaScript一段時間后,為了更深入的了解JavaScript,也為了更好的把握對JavaScript的應(yīng)用,從而在對閉包的學習過程中,自己對于詞法作用域的一些總結(jié)和理解,中間可能有一些地方不同于真實的JavaScript解釋引擎,因為我不是站在一個系統(tǒng)設(shè)計者的角度而是站在一個剛?cè)腴T的前端開發(fā)人員的角度上去分析這個問題,希望能對JavaScript開發(fā)者理解詞法作用域帶來一些幫助!
參考文獻:
[1] 弗蘭納根.JavaScript權(quán)威指南[M].6版.北京:機械工業(yè)出版社,2012.
[2] Nicholas C,Zakas.JavaScript高級程序設(shè)計[M].3版.北京:人民郵電出版社,2012.
[3] Stephen.淺談JavaScript的閉包和作用域鏈[EB/OL]. (2010-01-20).http://blog.endlesscode.com/2010/01/20/javascript-closure-scope-chain/447/1865447.shtml.