這所房子給人的感覺,就是它似乎已經沉入了無盡的睡夢,那氣氛壓制著你,不讓你醒過來,把你拖進被單下面,像喝醉了酒一樣,你沒有方法抗拒。 ——《忽然七日》
1.JavaScript的編譯過程
- 分詞/詞法分析,產生詞法單元token
- 解析/語法分析,生成抽象語法樹AST
- 代碼生成,將AST轉換為可執行的代碼
第一點需要介紹的是:作用域和this環境對象完全是兩碼事。關于JavaScript的編譯及運行過程,應該了解下面這幾個名詞:作用域,引擎,編譯器。對于JavaScript來說,大部分情況下編譯發生在代碼執行前的幾微秒的時間內。舉個例子:對于var a = 2;這樣的語句在引擎和編譯器中是怎樣被處理的?常見的錯誤答案是:編譯器為一個變量分配內存,將其命名為a,然后將值保存進這個變量——這個答案并不徹底的正確,更加體現出細節的答案應該是下面這樣的:
- 1.對于var a,編譯器會詢問作用域是否已經存在一個該名稱的變量在當前作用域的集合中。如果存在的話,編譯器會忽略掉該聲明,繼續下一步的編譯工作;如果不存在的話,那么編譯器會要求作用域在當前作用域中創建該變量,并且將其命名為a。
- 2.接下來編譯器會為引擎創建生成運行時所需要的代碼,這些代碼被用來處理a=2這個賦值操作。引擎運行這些代碼的時候,首先會詢問當前作用域在當前作用域中是否存在a這個變量,如果存在的話,那么引擎則使用那個變量進行賦值操作,如果不存在的話,那么引擎進一步向作用域詢問在當前作用域的上一級作用域是否存在變量a,如果有,則使用,如果沒有則繼續向上一級作用域查找。查詢的終點直到查詢到了全局作用域為止,如果在全局作用域中有這個變量的話,那么引擎將會使用它。如果沒有的話,那么將會拋出一個錯誤:ReferenceError。在這里在拓展一下關于這兩個常見錯誤的知識:ReferenceError和TypeError,前者是當在查詢變量的時候查的直到了全局作用域都查不到便會拋出ReferenceError錯誤;后者是這樣的情況:查詢到了變量,但是對這個查詢到的變量進行了不合法的操作,比如說對null和undefined進行屬性引用,或者對一個非函數類型變量進行函數調用。
關于引擎查找變量也是具有兩種類型的:RHS查詢和LHS查詢,就行為上來說:前者是讀,而后者是寫。
2.作用域嵌套
在JavaScript中,我們聽到的很多一句話那就是它沒有塊作用域,只有函數作用域。不過實際上,在JavaScript語言中還是具有一些殺馬特式的塊作用域的,比如with塊作用域,ccatch塊作用域,let為變量綁定的塊作用域,const為常量綁定的塊作用域。盡管JavaScript在設計之初,并沒有加入塊作用域這個實用的東西,但是不可否認的是,他的確存在,殺馬特一般的存在著。 作用域嵌套在實際編程中是一件很常見的事情,經常一個函數的聲明會在另一個函數的聲明里面。那么問題來了,當發生作用域嵌套的時候,變量查詢規則是怎樣的呢?答案就是:由內層進行查找,內層查不到的話轉戰外層,直到全局作用域為止,一查到便停止查找,查不到就拋出ReferenceError。
3.RHS查詢和LHS查詢的某些需要注意的地方
當使用RHS查詢的時候,如果一個變量直到查找到全局作用域都查找不到的話,那么便會拋出ReferenceError,但是對于LHS查詢而言,如果查到全局作用域都查找不到的話,那么就會在全局作用域中創建這個變量(僅限于非嚴格模式,嚴格模式下一樣拋出ReferenceError)。
4.作用域的工作模型
作用域的工作模型常見的有兩種,他們分別為詞法作用域和動態作用域。關于動態作用域工作模型,目前只有較少的語言使用它;而對于詞法作用域工作模式,他被大部分語言所所支持,正如我們的JavaScript一樣。
END