什麼是 Hoisting(提升)?
Hoisting 是 JavaScript 引擎在執行代碼前的一個特殊機制:
- 所有變數宣告(var, let, const)和函式宣告(function declaration)都會被「提升」到作用域的頂部
- 但只有 宣告 會被提升,賦值 仍保留在原位置
- var:宣告時初始化為
undefined - let/const:宣告但不初始化,進入「Temporal Dead Zone」直到實際宣告處
Step 1: var 的提升機制
步驟 1: 看下面的原始代碼:
console.log(myVar); // 輸出什麼?
var myVar = 10;
步驟 2: JavaScript 引擎在解析階段會將宣告提升到作用域頂部:
// 解析階段後的代碼
var myVar; // 初始化為 undefined
console.log(myVar); // undefined
myVar = 10;
步驟 3: 執行結果:
undefined
> undefined
> (anonymous) @ VM1:1
Step 2: let/const 的 TDZ (Temporal Dead Zone)
步驟 1: 看下面的原始代碼:
console.log(myLet); // 輸出什麼?
let myLet = 20;
步驟 2: JavaScript 引擎在解析階段會提升宣告,但不初始化,進入 TDZ:
// 解析階段後的代碼
let myLet; // 未初始化,進入 TDZ
console.log(myLet); // ReferenceError!
myLet = 20;
步驟 3: 執行結果:
ReferenceError: Cannot access 'myLet' before initialization
> Uncaught ReferenceError: Cannot access 'myLet' before initialization
> at <anonymous>:1:13
Step 3: 完整比較示例
步驟 1: 完整的比較代碼:
// var 的情況
console.log(varExample); // undefined
var varExample = 'Hello';
// let 的情況
console.log(letExample); // ReferenceError
let letExample = 'World';
// const 的情況
console.log(constExample); // ReferenceError
const constExample = '!';
步驟 2: JavaScript 引擎如何處理:
// 解析階段後的代碼
var varExample; // 初始化為 undefined
let letExample; // 未初始化,進入 TDZ
const constExample; // 未初始化,進入 TDZ
console.log(varExample); // undefined
varExample = 'Hello';
console.log(letExample); // ReferenceError!
letExample = 'World';
console.log(constExample); // ReferenceError!
constExample = '!';
步驟 3: 實際控制台輸出:
> undefined
> Uncaught ReferenceError: Cannot access 'letExample' before initialization
> Uncaught ReferenceError: Cannot access 'constExample' before initialization
Function Hoisting(函數宣告可以先呼叫)
函數宣告(Function Declaration)會被完全提升,包含函數的整個定義:
// 你寫的原始程式碼:
sayHello(); // 即使在宣告之前也可以呼叫!
function sayHello() {
console.log('Hello World!');
}
// JavaScript 引擎實際上會這樣處理:
function sayHello() {
console.log('Hello World!');
}
sayHello(); // 函數已完全定義,所以可以正常執行
> Hello World!
重點: 這意味著你可以先呼叫函數,然後再定義它。這是函數宣告(Function Declaration)的特有行為。
Function Expression(函數表達式)與 var/let 的差異
函數表達式(Function Expression)的行為與函數宣告(Function Declaration)完全不同:
var 函數表達式:
// var 函數表達式
myFunc(); // TypeError: myFunc is not a function
var myFunc = function() {
console.log('This is myFunc');
};
// JavaScript 引擎實際上會這樣處理:
var myFunc; // 提升為 undefined
myFunc(); // myFunc 是 undefined,不是函數,所以 TypeError
myFunc = function() {
console.log('This is myFunc');
};
> TypeError: myFunc is not a function
let 函數表達式:
// let 函數表達式
myLetFunc(); // ReferenceError: Cannot access before initialization
let myLetFunc = function() {
console.log('This is myLetFunc');
};
// JavaScript 引擎實際上會這樣處理:
let myLetFunc; // 提升但進入 TDZ
myLetFunc(); // 在 TDZ 中使用,所以 ReferenceError
myLetFunc = function() {
console.log('This is myLetFunc');
};
> ReferenceError: Cannot access 'myLetFunc' before initialization
總結:
- 函數宣告:整個函數都被提升,可以在宣告前呼叫
- var 函數表達式:只有變數名稱被提升(初始化為 undefined),賦值不會被提升,導致 TypeError
- let/const 函數表達式:變數名稱被提升但進入 TDZ,嘗試使用會導致 ReferenceError
操作
建議教學節驟:先讓學生「猜結果」→ 按按鈕驗證 → 你再總結:
• var:宣告會被提升(hoist),初值是 undefined
• let/const:有 TDZ(Temporal Dead Zone),宣告前使用會 ReferenceError
• function 宣告:整個函式會被提升,所以可先呼叫
輸出(Console-like)
也可打開 DevTools Console 對照(F12)。