學習 JavaScript 時,你可能曾經遇過這樣的神奇情況:一個函式或變數,明明寫在後面,卻可以在前面就被使用。
我們來看一段實際程式碼:
sayHi(); // 👉 這裡先呼叫函式
function sayHi() {
console.log("Hello!");
}照理說,我們應該先寫出 sayHi 這個函式的內容,才能在後面呼叫它。
但這段程式碼卻能正常執行,結果會印出:
Hello!再看一個變數的例子:
在日常寫程式的邏輯中,我們通常會預期「一個變數在使用之前必須先定義」,否則就應該報錯。
例如這段程式碼:
console.log(username);
let username = "Alice";這段會報錯:
ReferenceError: Cannot access 'username' before initialization這就符合多數人的直覺:我還沒說這個變數是什麼,當然不能先拿來用。
同樣的,如果你用 const 宣告也是一樣會報錯:
console.log(age); // ❌ ReferenceError
const age = 18;但奇怪的是,換成 var,結果竟然不一樣!
現在我們把 let 改成 var,什麼都沒多改,結果卻變得很奇怪:
console.log(message);
var message = "Hello JavaScript!";這段程式竟然可以執行,而且還印出了:
undefined明明變數 message 是在後面才定義的,為什麼在前面就能用了?而且還不是錯誤,而是一個 undefined 的結果。
這些「超自然現象」其實不是魔法,而是 JavaScript 的一個設計機制 —— Hoisting(提升)。
在這篇文章中,我們會一步步解釋:
- 什麼是 Hoisting?
- 哪些東西會被提升?
- 提升的過程實際是怎麼運作的?
- 函式宣告與表達式有什麼不同?
var、let、const的提升行為有什麼陷阱?
什麼是 Hoisting?一種「預先搬運」的機制
在 JavaScript 中,當你執行一段程式時,其實程式碼不是直接一行一行從上跑到下那麼簡單。
事實上,在真正開始執行之前,JavaScript 會先進行一個重要的準備階段,稱為 編譯階段(Compilation Phase)。
在這個階段,JavaScript 解譯器會預先掃描整份程式碼,並將某些特定的宣告(例如函式或 var 變數)「自動搬到程式的最上面」,這個動作就叫做:
🔁 Hoisting(提升)
簡單說,Hoisting 就是 JavaScript 在背景中偷偷做的一個「搬家動作」:把宣告提前,讓你可以「先用後寫」。
生活化比喻:
想像你早上要出門上班或上學。
雖然你 8:00 才會穿鞋出門,但你通常在一開始準備時就會先把鞋子、鑰匙、手機錢包放在門口的固定位置,不是到要出門的那一刻才臨時找東找西。
這就是一種「事前準備」的行為:你知道之後會用到什麼東西,雖然用的時間在後面,但你會提早把它們先準備好、放好位置。
JavaScript 的 Hoisting 也是類似的邏輯 —— 在程式正式開始執行前,JavaScript 會先把你宣告的函式或變數名稱「提前放到正確的位置」,等真正需要的時候就可以馬上拿來用,不會手忙腳亂。
程式範例說明:
假設你寫了這樣一段程式碼:
sayHello();
function sayHello() {
console.log("Hello world!");
}💡 初學者直覺會以為:
「我還沒寫 sayHello() 的內容,怎麼可以先呼叫它?應該會出錯吧?」
但實際上這段程式會順利執行,印出:
Hello world!JavaScript 背後其實做了這件事:
在進入執行階段之前,JavaScript 自動把 function sayHello() 搬到最前面,變成這樣:
// JavaScript 心中其實這樣處理:
function sayHello() {
console.log("Hello world!");
}
sayHello(); // 現在就變得合理了!你看到的程式碼和 JavaScript 實際「心裡」理解的執行順序,其實是不同的,這就是 Hoisting 的效果。
為什麼 JavaScript 需要 Hoisting?
你可能會覺得奇怪:為什麼 JavaScript 要把「函式或變數搬到最前面」?
不能像其他語言一樣,寫什麼就照順序跑嗎?
這樣不但容易懂,也比較符合直覺。
其實 JavaScript 的這種「先準備再執行」的設計,是有原因的,而且它的出發點其實是為了讓你——寫程式更輕鬆、更不容易出錯。
背景補充:JavaScript 原本是設計給「非工程師」用的
JavaScript 在 1995 年剛被發明時,它的定位並不是像 Java 那樣的「嚴謹程式語言」,而是:
✅ 一種輕量、簡單、讓網頁互動更方便的小工具語言。
換句話說,它本來就是為了「不懂程式的人也能快速上手」而設計的。所以它很多機制都偏向寬容、彈性,也允許你用比較自由的方式撰寫程式。
所以 Hoisting 的存在,目的有三個:
✅ 讓你可以先寫「邏輯」,後面再補「細節」
你可以先寫下你想做的流程,像這樣:
startGame(); // 先寫主流程
function startGame() {
// 遊戲邏輯
}這樣的寫法很像在講故事:先講發生什麼事,再慢慢說細節怎麼做。這樣對人來說比較好讀,對寫程式的新手也更友善。
✅ 讓函式定義集中管理,程式架構更清晰
當你寫的程式越來越大時,你可能會希望:
- 上面是主要的邏輯流程
- 下面集中放所有工具函式(例如:格式化字串、檢查輸入、顯示錯誤訊息)
因為有 Hoisting,你可以把函式通通集中寫在最下面,整體邏輯就不會被打斷,也比較好維護:
main(); // 主流程在上面
function main() {
sayHi();
validateInput();
}
function sayHi() {
console.log("Hi!");
}
function validateInput() {
// ...
}🤔 那為什麼 Hoisting 有時會讓人感到困惑?
因為 不是所有的東西都會被提升得一樣。
函式宣告會整個被提升,但變數只提升「名稱」不提升「值」,而 let 和 const 根本不提升(或進入暫時性死區),所以容易出現「前面可以用、但又不是你想像中的用法」的情況。
換句話說:Hoisting 是貼心,但也有些潛規則需要你搞懂。
小結
| 為什麼需要 Hoisting? | 重點說明 |
|---|---|
| ✅ 對新手更友善 | 不用太在意順序,先用再定義也行 |
| ✅ 程式邏輯更靈活 | 可以先寫主流程,函式放後面 |
| ✅ 減少不必要的錯誤 | 預先準備好變數與函式,不會因順序亂掉 |
| ⚠️ 但也容易踩雷 | 不同宣告類型提升行為不同,要搞懂差異 |
會被提升的東西:var
當你使用 var 宣告變數時,JavaScript 會在程式一開始先建立變數的「殼」,但不會給它值。
這會導致你在變數賦值之前使用它,雖然不會報錯,但值是 undefined。
🧪 範例:
console.log(name); // 👉 undefined
var name = "Alice";實際上 JavaScript 背後是這樣理解的:
var name; // 提升了這一行
console.log(name); // 👉 undefined,因為還沒賦值
name = "Alice";📌 這種行為容易誤導人,以為變數有值,其實只是存在但還沒被賦值。
會被提升的東西:函式宣告(Function Declaration)
這是 Hoisting 最「完整」的情況 —— 整個函式連同名稱與內部邏輯都會被提前處理好,所以你可以放心在定義之前就使用它。
🧪 範例:
greet(); // 👉 可以正常執行
function greet() {
console.log("Hello!");
}這段程式在執行時就像是:
function greet() {
console.log("Hello!");
}
greet(); // 👉 現在看起來就合理了📌 這種寫法非常適合在主流程中先呼叫函式、再在下面統一管理定義。
不會被提升的東西:let / const
在文章前面,我們提過,大多數人對程式的直覺是這樣的:
「變數要先宣告,才能使用,不然應該會報錯。」
其實這種直覺是合理的,而且後來 JavaScript 也確實為了貼近這種直覺,在 ES6(2015)中推出了新的變數宣告方式:let 和 const。
這兩種方式的最大特點,就是它們不會像 var 一樣偷偷幫你把變數提到前面用。
你什麼時候宣告,它就什麼時候才能用。
這樣一來,程式行為就會更清楚、也更符合人的邏輯。
🧪 範例:這段會報錯是「合理」的
console.log(age); // ❌ ReferenceError
let age = 25;執行結果會出現錯誤:
ReferenceError: Cannot access 'age' before initializationJavaScript 的意思是:「我知道你有 age,但你還沒正式宣告完成,現在不准碰。」
這段變數不能用的區域,叫做「暫時性死區(TDZ)」
在 JavaScript 裡,如果你用 let 或 const 宣告變數,有一個很特別的規則:
你不能在宣告那一行之前使用它,不然就會直接報錯!
來看這段簡單的例子:
console.log(name); // ❌ 會報錯!
let name = "小明";這裡會報:
ReferenceError: Cannot access 'name' before initialization🤔 為什麼會這樣?
因為 JavaScript 其實一開始就知道你「等一下」會宣告 name。
但它規定:「你還沒正式說出來之前,我不准你用!」
這段「變數已經存在,但還不能用」的期間,就叫做:
❗暫時性死區(TDZ, Temporal Dead Zone)
🏪 更生活化的比喻:變數就像一間店,還沒營業不能進去!
想像你走到一家早餐店門口,店員都已經在裡面備料、打掃、準備開店了。
雖然你看得出來「這家店已經在裡面動起來了」,但只要鐵門還沒拉起來,你就是不能走進去點餐。
這就像 let 和 const 宣告的變數:
變數已經在「程式的範圍」裡,但只要還沒正式宣告完成,就完全不能碰,碰了就錯。
這段「店裡有人但你不能進」的時間,就是我們說的:
❗暫時性死區(TDZ, Temporal Dead Zone)
等到鐵門一拉起(程式執行到 let 或 const 那一行)。
你才可以開始使用那個變數,就像你進店點餐一樣自然。
| 真實情境 | JavaScript 對應行為 |
|---|---|
| 店員在準備 | JavaScript 已經知道變數存在 |
| 鐵門還沒拉起 | 還沒執行到那行 let / const 宣告 |
| 客人不能進店 | 變數還不能使用,提前使用會報錯 |
| 鐵門打開才營業 | 執行到宣告那行後,變數才可以被正常使用 |
🧪 程式碼範例:
來看這段程式碼:
{
console.log(food); // ❌ 這裡還不能用
let food = "壽司"; // ✅ 現在才正式宣告,可以用了
console.log(food); // 👉 輸出:壽司
}這是一個區塊作用域(由 {} 包起來的區塊),我們在裡面用 let 宣告了一個變數 food。
🔍 這段程式會怎麼執行?
- JavaScript 進入這個
{}區塊時,會先「記住」你等等會宣告一個叫food的變數。 - 但在這之前(也就是還沒跑到
let food = ...那一行之前),你就先用console.log(food)想讀取food。 - 這時候 JavaScript 會直接報錯,因為你正在存取一個還沒啟用的變數。
🧠 白話解釋這段發生了什麼事:
你可以把整段流程想像成:
JavaScript:「我知道你之後會講 ‘food’,但你現在還沒正式說明 ‘food’ 是什麼。你現在就想用它?不行,會出事。」
只有等到這一行真的被執行:
let food = "壽司";JavaScript 才會「正式啟用」這個變數,讓你在那之後自由使用它。
🎁 對應回我們剛剛說的「暫時性死區(TDZ)」
在 {} 這個區塊中,變數 food 是在作用域裡沒錯,但從進入區塊那一刻開始,到 let food = ... 這行被執行為止,food 都處於「還不能用」的狀態 ——
這段時間,就叫做「暫時性死區」。
只要你在這段期間嘗試去用它,不論是印出來、比大小,甚至只是問問它是不是存在,JavaScript 都會直接報錯。
為什麼 JavaScript 要設計 TDZ?
「既然變數還不能用,那幹嘛要先知道它存在?為什麼不等跑到那一行再處理就好?」
這是很多初學者學到 let / const 和 TDZ(暫時性死區)時最常問的問題。
乍看之下,這種機制好像多此一舉,但它其實是為了 讓 JavaScript 的執行模型更一致、更嚴謹,也更安全好維護。
作用一:在作用域中預留位置,但先「鎖住」變數
很多人以為 JavaScript 是「從上往下一行行執行」的直譯語言,但其實不是。
在真正執行前,JavaScript 引擎會做一個隱藏的動作叫做:
編譯階段(Compile Phase)
這時候,JavaScript 會先掃一遍你的整份程式碼,記錄下哪些地方定義了變數、函式、區塊、作用域等等。
然後才進入 執行階段(Runtime Phase),一行一行執行你的程式。
當你用 let 或 const 宣告變數時,JavaScript 在編譯階段就會知道:
「喔,這邊會有個變數
x,我先幫你預留記憶體位置。」
不過它不會馬上幫你設定值,也不讓你提早拿來用。
這段「知道它會出現,但還不能用」的期間,就是暫時性死區(TDZ)。
可以能會想問:為什麼不乾脆「等執行到那一行再知道」就好?
原因在於,若變數直到跑到那行宣告才「出現」,那整個區塊的變數狀況會變得不穩定。
想像你進入一間教室,老師說:「今天會有 5 位同學上台報告。」
這樣你從一開始就知道有哪些人會出現。
但如果老師改成:「走到講台再看有沒有人」,你每一分鐘都會緊張地想:「到底會不會有人上來?會有誰?」
這樣就會讓整個流程混亂、不確定、難以安排。
JavaScript 也是這樣:一進區塊,就先把變數名列出來(雖然還不能用)
這樣的行為讓 JavaScript 在一進到一段區塊時(像是一個函式或 if 區塊),就能提前知道有哪些變數會出現,雖然它們還沒被賦值,但至少「知道它們會來」。
來看個例子:
function test() {
console.log(x); // ❌ ReferenceError:這裡知道有 x,但還不能用!
if (true) {
let x = 5;
}
}這段的行為是:
- JavaScript 一開始就知道
x會在if裡被宣告(它會被放進 scope 裡) - 但在
let x = 5這一行之前,x還不能用(進入 TDZ) - 所以前面
console.log(x)就會報錯,而不是靜靜地印出 undefined
🔍 如果沒有 TDZ,會發生什麼事?
如果 JavaScript 是「走到哪才知道哪有變數」,那前面 console.log(x) 就會變成以下幾種不確定狀況:
- 有時候
x存在,有時候根本沒宣告 → 很難預測 - 有些情況會印出
undefined,有些又報錯 → 開發者容易混亂 - 程式維護時不清楚作用域內到底有哪些變數 → 很難 debug
這就像你寫一部小說,如果每段內容裡的角色都可能忽然冒出來又不見,讀者會很痛苦。
✅ TDZ 的設計,讓程式像一本規劃清楚的劇本
TDZ 保證一件事:
「只要變數用
let或const宣告,它一定會在區塊一開始就被記錄下來,但你只能在它被賦值後再使用。」
這就像劇本中的角色設定:
- 一開場就把角色名列出來(但還沒出場)
- 出場順序照著劇情來
- 誰先講話、誰後出現,全都規劃好
這種結構讓 JavaScript 更有秩序、可預測,也讓你更容易寫出乾淨、容易維護的程式碼
作用二:阻止你誤用還沒準備好的變數
在舊版 JavaScript(使用 var)中,如果你在變數尚未真正設定好值之前就使用它,程式不會報錯,只是給你一個 undefined。但這其實很危險,因為你以為變數沒值,結果只是太早用了。
🧪 舊寫法(容易出 bug):
if (status === "ready") {
startGame();
}
var status = "ready";你可能以為這段程式碼會順利啟動遊戲,但實際上不會,因為:
status雖然已經被「提升」,但值還是undefined- 所以
status === "ready"是 false startGame()根本不會被執行!
而且這整段程式不會報錯,你會以為哪裡壞掉了,但找不到問題。
🛡️ 現在用 let + TDZ,錯誤會馬上跳出來:
if (status === "ready") {
startGame();
}
let status = "ready";這段就會直接報錯:
ReferenceError: Cannot access 'status' before initializationJavaScript 引擎在你使用 let 宣告變數時,會在程式「一開始」就知道你打算宣告 status,但直到那一行真正執行之前,它會「鎖住」這個變數,不讓你亂用。
作用三:讓瀏覽器更容易做效能最佳化
JavaScript 並不是完全從上往下直接執行的語言,它在開始跑程式之前,會先經歷一個編譯階段,做以下事情:
- 掃描你的程式碼
- 建立作用域(Scope)環境
- 記下有哪些變數和函式會出現
- 幫每個變數預留空間
- 判斷哪些東西什麼時候可以被使用
如果你使用 let 或 const 宣告變數,JavaScript 就會在「一開始」把它們標記在對應的作用域中,即使值還沒設定,它也會記下:
「這個區塊裡會出現某個變數,但要等到第幾行才正式啟用。」
這種「先知道、再初始化」的設計,讓 JavaScript 有更多機會提前準備,也更能有效率地使用記憶體與資源。
🚀 TDZ 幫助的底層優化有哪些?
- 記憶體配置更有效率
因為引擎一開始就知道有哪些變數會用到,就可以提早幫你配置好記憶體位置,不需要等到中途才緊急找空間。 - 靜態分析更精確
編譯階段可以先掃出所有變數的範圍與壽命(什麼時候會出現、用到哪裡),對像 Babel、Terser 這種壓縮工具來說,也能更有效壓縮沒用到的變數。 - 產生更快的執行碼
引擎根據作用域與變數資訊,可以提早編排執行流程(例如做 inlining、移除不必要的變數查找等),整體程式跑得更快。
🧠 一個簡單比喻:排隊入場 vs 現場亂找人
想像你在參加一場大型活動:
- 如果主辦單位在進場前就先對名單、座位安排、流程動線做足準備(就像 JavaScript 的編譯階段),整場活動會非常順暢。
- 如果主辦單位什麼都沒規劃,等到人來了才臨時決定(像是等變數出現再處理),那現場就會一團混亂,效率低落。
TDZ 就像是這個名單規劃機制:
「先知道你會出現,等你報到之後再放你進場。」
這樣做,既能避免意外亂入,也能幫整體流程提速、減少出錯。
明白了,我來幫你用更白話、更口語的方式重新整理這兩段內容,讓初學者也能輕鬆理解:
不會被提升的東西:函式表達式(Function Expression)
你可能會寫過這樣的程式:
sayHi(); // ❌ TypeError: sayHi is not a function
var sayHi = function () {
console.log("Hi!");
};錯誤解釋
這裡報的是 TypeError,原因是:
- JavaScript 在「編譯階段」會先把
var sayHi這個變數的名字搬到最上面 - 所以你在
sayHi();那一行呼叫它時,這個變數是「有的」,不會說 undefined - 但是值還沒被指定,也就是
sayHi裡面還是undefined - 然後你試圖執行
undefined(),這才是錯的
就像你說:「我知道有個東西叫 sayHi,但它現在不是個可以執行的函式」,所以會報出:
❌ TypeError: sayHi is not a function不會被提升的東西:箭頭函式(Arrow Function)
箭頭函式寫法比較簡潔,看起來像是函式,但它本質上跟剛剛那個函式表達式一樣 —— 是「把一個函式當作值,丟進變數裡」。
add(2, 3); // ❌ ReferenceError: Cannot access 'add' before initialization
const add = (a, b) => a + b;錯誤解釋:為什麼會出現 ReferenceError?
來看一次這段程式碼:
add(2, 3); // ❌ ReferenceError: Cannot access 'add' before initialization
const add = (a, b) => a + b;這裡 JavaScript 直接報錯,說:
ReferenceError: Cannot access 'add' before initialization意思就是說:
你想用一個變數,但這個變數還沒『初始化』,所以不能用。
什麼是「初始化」?
你可以這樣理解:「初始化」其實就是:
✅ 變數準備好了,裡面有值了,可以開始使用了。
像這樣的程式碼:
const name = "Amy";JavaScript 在處理這一行的時候,其實會分兩個步驟來進行:
- 宣告(declare):JavaScript 會先知道你接下來要用一個變數,叫做
name。
(但此時變數還沒有值,還不能使用) - 初始化(initialize):JavaScript 會將
"Amy"這個值放進name這個變數裡。
(這時候變數才真的準備好,可以開始被你使用)
簡單說,就是:
- 宣告:告訴 JavaScript 變數會存在(但還沒有值)
- 初始化:把值真正放進變數裡,正式啟用
🔥 所以剛才為什麼會報錯?
回到你原本的程式碼:
add(2, 3); // ❌ ReferenceError: Cannot access 'add' before initialization
const add = (a, b) => a + b;你在第一行的時候就嘗試呼叫 add 這個變數,但這時候:
add變數確實已經「宣告」了(JavaScript 已經知道你會用這個名字)- 但它還沒「初始化」,也就是裡面根本沒有值
- 所以 JavaScript 馬上阻止你使用,直接報錯,避免你使用一個尚未準備好的變數。
就像你準備了一個空杯子,但裡面還沒倒飲料,朋友就急著拿起來喝,當然不行!
延伸補充:「那用 var 宣告的變數為什麼不會報錯?」
我們再一次用剛才的例子,但這次改成用 var:
add(2, 3); // ❌ TypeError: add is not a function
var add = (a, b) => a + b;你會發現,這一次錯誤訊息改變了,變成:
❌ TypeError: add is not a function而不是剛才的 ReferenceError。
🔍 為什麼變成 TypeError?
原因是這樣的:
用 var 宣告的變數在 JavaScript 執行前的「提升」過程中,會做兩件事:
- ✅ 宣告 這個變數名稱
- ✅ 同時自動幫這個變數 初始化(initialize)成
undefined
也就是說,程式實際開始執行前,變數已經存在,而且裡面已經先放入了一個 undefined 的值。
當你呼叫:
add(2, 3);這一行的時候,JavaScript 引擎會這樣理解:
「我確定已經有一個變數叫
add,而且裡面已經有個值了,但這個值現在是undefined。」
你試著把這個 undefined 當成函式來呼叫(undefined()),當然就會出錯:
❌ TypeError: add is not a function📌 與剛才 const 宣告的差異比較:
| 宣告方式 | 宣告階段完成? | 初始化階段完成? | 提前使用會發生什麼事? | 錯誤類型 |
|---|---|---|---|---|
var | ✅ 完成 | ✅ 已完成(值是 undefined) | 存取到 undefined,但無法當成函式呼叫 | ❌ TypeError |
let / const | ✅ 完成 | ❌ 未完成(沒值) | 完全禁止存取,直接報錯 | ❌ ReferenceError |
白話總結一下:
- 用
var宣告變數時,變數會自動被初始化成undefined,因此你提前存取它不會有問題(但若你把它當函式用,就會報TypeError)。 - 用
let或const時,變數會被鎖起來,連存取的機會都沒有,直接報錯ReferenceError,告訴你「這個變數還不能碰!」
這樣設計就是為了讓你盡早發現錯誤,不要默默使用錯誤的值而不自知。
JavaScript 提升(Hoisting)總結小抄
在 JavaScript 中,有些變數或函式可以在宣告前就直接使用,有些卻不行。
這張小抄幫你一次搞懂差別在哪裡:
| 宣告類型 | 會被提升? | 提升的內容有哪些? | 能不能在宣告前使用? | 初學者要注意的地方 |
|---|---|---|---|---|
✅ function 函式宣告 | ✅ 會提升 | 整個函式(名稱 + 裡面的內容) | ✅ 可以放心用 | 最直覺、安全,不容易踩雷。 |
⚠️ var 變數宣告 | ✅ 會提升 | 只有變數名稱,值是 undefined | ⚠️ 可以用,但小心! | 提前使用時只會得到 undefined,容易搞錯。 |
❌ let / const 變數宣告 | ❌ 不會提升 | 不會提升(但名稱被鎖在暫時性死區 TDZ) | ❌ 不行,會直接報錯! | 因為存在 TDZ(暫時性死區),使用前一定要先宣告才安全。 |
| ❌ 函式表達式 / 箭頭函式 | ❌ 不會提升 | 不會提升(實質是變數的賦值) | ❌ 不行,會報錯! | 本質上是「把函式當成值」,和一般變數一樣無法提前使用。 |
function 函式宣告
函式宣告的寫法像這樣:
hello(); // ✅ 可以正常運作!
function hello() {
console.log("Hi!");
}為什麼能用?
因為 JavaScript 一開始就會幫你把整個函式(包含內容)搬到程式最上面,所以你怎麼用都很安全、很直覺。
var 變數宣告
var 宣告的變數只會「一半提升」:
console.log(name); // ⚠️ 印出 undefined(沒錯誤,但危險!)
var name = "Amy";為什麼不會報錯?
因為 JavaScript 只幫你提升了 name 這個變數名稱,並且先自動給它一個預設值 undefined。
但這也會有問題,你可能誤以為它有值,結果只是空的。
let 和 const 變數宣告
用 let 或 const 宣告時,就完全不會被提升:
console.log(age); // ❌ ReferenceError(完全不能用!)
let age = 18;為什麼直接報錯?
雖然 JavaScript 知道你會宣告這個變數,但它故意把變數鎖起來,不讓你提前用(暫時性死區,TDZ),這是為了避免你犯錯,提前抓 bug。
函式表達式或箭頭函式
函式表達式或箭頭函式的本質,其實是把函式當作值放進變數:
sayHi(); // ❌ TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};或
add(2, 3); // ❌ ReferenceError: Cannot access 'add' before initialization
const add = (a, b) => a + b;為什麼這樣用會報錯?
因為只有變數名稱可能會提升(如果你用 var),但函式的內容絕對不會提前設定進去。
- 如果用
var宣告,你會拿到undefined,當成函式執行就變成TypeError。 - 如果用
const或let宣告,它還在暫時性死區(TDZ),你完全不能碰它,直接跳ReferenceError。
用一句話記住差別
function宣告:JavaScript 幫你完整準備好函式,最安全var宣告:只準備了一半 (undefined),很容易誤用let/const宣告:完全不能提早使用,最安全但最嚴格- 函式表達式/箭頭函式:函式當成值來看待,無法提前使用,必須先宣告再用
這樣你之後看到任何狀況,都能輕鬆分辨、不再搞錯囉!
結語
Hoisting 是 JavaScript 中的一個特殊行為,讓程式可以在邏輯上更彈性安排,但同時也容易讓初學者踩雷。
只要掌握這三個原則,你就能安全又有效地撰寫程式:
- 想用提升?就用函式宣告。
- 避免
var,多使用let/const。 - 不要在宣告前就使用變數或函式表達式。
記住這些,你就能在撰寫 JavaScript 程式時避免常見錯誤,邁向進階之路!