引言
--
寫 JS 誰沒被變數提升坑過?明明變數寫在後面,前面卻印出 undefined;函式定義在末尾,開頭就能直接呼叫。很多人背了「宣告會提升」的口訣,卻以為是程式碼被偷偷搬到了頂部 —— 大錯特錯!這根本不是什麼玄學 bug,而是 V8 引擎在程式碼執行前悄悄搞的預編譯小動作。
那什麼是變數提升呢?接下來用一段程式碼告訴你:
js 体验AI代码助手 代码解读复制代码 console.log(a);
var a=2;
輸出結果為:

你看,我們明明先印出 a、後定義 a,輸出卻不是預期的報錯,而是 undefined。這就是前端入門必踩的「變數宣告提升」坑。它的本質是:V8 引擎在程式碼執行前,會先偷偷把變數 a 的宣告提到目前作用域的最前面,賦值操作卻原封不動留在原地。所以這段程式碼的實際執行順序,相當於引擎幫你偷偷改成了這樣:

函式的預編譯,只有在函式被呼叫時才會觸發,核心是建立 AO 物件(執行上下文物件),用一段程式碼解釋:
js 体验AI代码助手 代码解读复制代码function foo(a, b) {
console.log(b);
c = 0
var c
a = 3
b = 2
console.log(b);
function b() {}
console.log(b);
}
foo(1)
步驟如下:
- 形參:a、b → AO 新增屬性 a: undefined,b: undefined
- 變數宣告:var c : undefined
- 此時 AO:{a: undefined, b: undefined, c: undefined}
- 函式呼叫時實參是 1,對應形參 a → a = 1
- 實參只有一個,形參 b 沒有對應實參,仍為 undefined
- 此時 AO:{a: 1, b: undefined, c: undefined}
- 函式宣告:function b() {} → AO 新增屬性 b: function b() {}
- 此時 AO:{a: 1, b: function b() {}, c: undefined }
最後輸出結果為:

如果還是覺得抽象,我給你打個最通俗的比方:我們把 V8 引擎比作一家公司的大老闆,他脾氣很倔,只認最終的執行指令,拿到手就直接幹,絕不幫你整理亂七八糟的材料。而預編譯,就是老闆身邊那個能力超強的全能女秘書。
當你把一整段 JS 程式碼(也就是一堆待處理的工作任務)丟給公司時,絕不會直接堆到老闆桌上。秘書會第一時間把所有任務全部過一遍,雷打不動做兩件核心工作:
全域預編譯,在程式碼載入完成後、執行之前觸發,核心是建立 GO 物件(全域執行上下文物件),同樣結合案例:
js 体验AI代码助手 代码解读复制代码function a () {}
console.log(a);
var a = 1
var b = 2
步驟如下
- 變數宣告:var a,b → GO 新增屬性 a: undefined,b: undefined;
- 此時 GO:{a: undefined, b: undefined}
- 函式宣告:function a() {} → GO 屬性中 a 重新被賦值:function a() {}
- 此時 GO:{a: function a(){}, b: undefined}
最後輸出:

到這裡,我們就徹底搞懂了 JS 變數提升和預編譯的底層邏輯。所有看似反直覺的變數提升現象,本質上都不是程式碼被物理移動到了作用域頂部,而是 V8 引擎在程式碼執行前,透過預編譯階段提前完成了所有宣告的記憶體初始化。