JavaScript引擎在程式碼執行之前會先進行如下操作:
- 先進行
分詞/詞法分析
將語句分割成詞法單元token
,在對當前的整個作用域分析完成後,JS引擎會將token
進行解析/語法分析
翻譯成AST
(抽象語法樹) - 預編譯(預處理)
- 邊解釋邊執行(不是純解釋,還有JIT編譯,這裡不展開了)
預編譯(預處理)
有人說JavaScript沒有預編譯,是屬於語法分析
的一部分,有人說有詞法,語法和程式碼生成
就已經屬於編譯了,只是不需要提前編譯的,而是在執行前的幾毫秒才編譯。所以JavaScript到底有沒有預編譯或者這些步驟屬不屬於預編譯(預處理)大佬們的意見也不同,我們就不探討了,暫且把語法分析生成AST後到執行前的一些處理過程稱之為“ 預編譯 ”
把。
全域性預編譯
- 建立
Global Object
全域性上下文物件 - 找變數宣告,將變數名繫結為
GO
的一個屬性,值為undefined
- 找函式宣告,將函式名繫結為
GO
的一個屬性,值為函式體
- 執行全域性程式碼
- 建立
區域性預編譯
- 建立
Activation Object
函式上下文物件 - 找行參,將行參名繫結為
AO
的一個屬性,值為undefined
- 找變數宣告,將變數名繫結為
AO
的一個屬性,值為undefined
- 實參值賦值給形參
- 找函式宣告,將函式名繫結為
AO
的一個屬性,值為函式體
- 函式生成
[[scope]]
屬性,值為一個陣列,陣列的第一項是本函式的AO,下一項為外層函式的AO,一直往外層,直到最後一項為GO - 執行函式程式碼
- 建立
宣告提升,作用域鏈
上面預編譯的前半部分就是所謂的宣告提升
,我有文章單獨講了宣告提升就不贅述了,詳情點選這裡。
函式執行前會生成`[[scope]]`屬性,值為一個陣列,陣列的第一項是本函式的 AO
下一項為外層函式的 AO,一直往外層,直到最後一項為 GO
AO
是函式的作用域,而[[scope]]
這樣的鏈式陣列則是函式的作用域鏈,當函式進行值查詢RHS
時會先在[[scope]]
中的第一項中尋找,如果沒有找到則查詢第二層,以此類推直到查詢到GO為止。
閉包
當函式FN
執行結束後,這個函式FN
的AO
的生命週期就應該結束了,需要被銷燬。但是有特殊情況,雖然說是特殊情況,但是開發中非常常見,就是函式FN
執行結束後return
了一個內部函式A
並被外部儲存後,本來應該被銷燬的函式FN
的AO
便無法被銷燬,因為函式A的[[scope]]
儲存著函式FN
的AO
作為自己原型鏈的一部分,只要儲存函式A
的變數不被銷燬或者變數所在的AO
不被銷燬則函式FN
的AO
永遠不會被銷燬,並且變數儲存的函式A
可以一直訪問函式FN
中的變數和值,這樣的現象叫做閉包
,這樣的閉包大量出現會導致記憶體洩漏或者載入過慢。
var Variable_1 = 1;
function FN() {
var Variable_2 = 2;
function A() {
var Variable_3 = 3
console.log(Variable_2);
}
return A;
}
var Function_A = FN();
FnVariable(); // 接受 函式A 的變數 Function_A 通過呼叫可以輸出 FN 中的 Variable_2