JS引擎的編譯時機
JavaScript也是需要編譯才能執行的!!!它與某些程式語言(Java、C/C++、Golang等)不同,這些語言在執行時一定是編譯好的檔案;JavaScript是解釋型語言,執行之前仍然是使用者編寫的原始碼,並不是一個編譯後的檔案;它會一邊執行一邊編譯,編譯時機在程式碼執行環境進入一個新的作用域到開始執行第一行程式碼中間的時間片(微秒級、也能解釋為什麼程式碼錯誤只能在執行時才發現)
變數提升討論
var/function
透過var/function宣告的變數,會出現變數提升的現象,也就是說在程式碼塊執行之前(編譯)會識別var和funciton宣告的變數,並將其宣告在程式碼塊頂部,初始化為undefined
如: { console.log(1); var a = 1; var b = 2; } 編譯為: { var a; // 變數提升初始化為undefined var b; // 變數提升初始化為undefined console.log(1); a = 1; b = 2; }
let/const
目前網上很多解釋都說let/const宣告的變數不存在變數提升,但我不贊同!我認為let/const同var/funciton一樣,也是具有變數提升屬性的,區別在於let/const有一些“特殊處理”
暫時性死區:透過let/const宣告的變數存在暫時性死區,通俗來說就是宣告的這個變數暫時不可使用;仔細理解“暫時不可用”,可以得到結論:變數已經宣告了,只是不可用。也就是說變數在沒有定義就已經被宣告,足以證明變數提升的存在。
如果你仍然不支援let/const也存在變數提升,請繼續往後看:
JS引擎與let/const
前面簡單提到JS程式碼是一邊編譯一邊執行的,現在我們需要了解,編譯過程會同步生成一個“符號表”,用於記錄當前作用域所包含的所有變數(包括let/const宣告的變數),這個表中記錄 識別符號名稱(變數/函式等名稱)、宣告型別(var/let/const)、初始化狀態(是否被初始化)、初始化值(預設undefined)
(這裏只列出便於本文分析的資料,符號表相當複雜且不同引擎的實現存在差異)
let/const特性在JS引擎的實現
function func() {}; var a = 1; let b = 2; const c = 3;
程式碼未執行的符號表資料
識別符號名稱 | 宣告型別 | 初始化狀態 | 初始值 |
---|---|---|---|
func | function | true | func: Function |
a | var | true | undefined |
b | let | false | undefined |
c | const | false | 3 |
程式碼執行完畢的符號表資料
識別符號名稱 | 宣告型別 | 初始化狀態 | 初始值 |
---|---|---|---|
func | function | true | func: Function |
a | var | true | 1 |
b | let | true |
2 |
c | const | true |
3 |
let/const暫時性死區特性
透過上表可以分析出,暫時性死區其實就是對應“初始化狀態”列,在“符號表”被建立時:var/function方式宣告的變數對應初始化狀態為true。let/const型別宣告的變數初始化狀態為false。JS引擎判斷暫時性死區虛擬碼
/** * 執行在讀取變數值方法 */ // declared - 是否已宣告 | initialized - 是否初始化 if (!declared(a) || !initialized(a)) { throw new ReferenceError("a is not defined."); }
const初始化資料和不可修改特性
宣告但未給初始化值:這個異常在編譯階段就會出現,在生成“符號表”過程中,判斷變數宣告型別是否為const,如果是且未給定初始值,則丟擲錯誤
/** * 執行在編譯階段 */ // declareTypeIsConst - 是否透過const宣告 | enableValue - 是否給定初始值 if (declareTypeIsConst(a) && !enableValue(a)) { throw new SyntaxError("Missing initializer in const declaration."); }
後續修改:在變數修改方法中,判斷當前變數宣告型別是否是const,如果是,則丟擲錯誤
/** * 執行在修改變數值方法 */ // declareTypeIsConst - 是否透過const宣告 if (declareTypeIsConst(a)) { throw new TypeError("Assignment to constant variable."); }
一些問題
Q: ES6編譯為ES5後,還能保持let/const的特性嗎?
A: 不能完全保持let/const的特性
解釋1(特性消失):let和const在宣告之前使用會報錯
ES6: 報錯(期望狀態)
ES5:未報錯
解釋2(特性存在):for語句中let是塊級作用域,可以做到依次輸出0~5
ES6:正常輸出
ES5:正常輸出