JavaScript 變數宣告:var、let、const
簡介
在 JavaScript 中,變數是程式的基本構件,它負責保存資料、控制流程、以及在不同函式之間傳遞資訊。從最早的 var 到 ES6 引入的 let、const,變數的宣告方式不斷演進,目的是讓程式碼更安全、更易讀,也更符合開發者的直覺。
對於初學者而言,了解三者的差異不僅能避免常見的錯誤,還能在實務開發中寫出 可維護、可預測 的程式。本文將從語法、作用域、提升(hoisting)以及實務最佳實踐等層面,深入說明 var、let、const 的使用方式,並提供多個實用範例,幫助你在日常開發中正確選擇變數宣告方式。
核心概念
1. var – 傳統的變數宣告
| 特性 | 說明 |
|---|---|
| 作用域 | 函式作用域(function scope),若在函式外宣告則為全域作用域 |
| 提升 (hoisting) | 變數會被提升至所在作用域的最上方,但只提升宣告,不會提升賦值 |
| 可重新賦值 / 可重新宣告 | 同一作用域內可多次宣告同名變數,且隨時可改變值 |
範例 1:var 的提升與作用域
console.log(message); // => undefined(被提升但未賦值)
var message = 'Hello, world!';
function demo() {
console.log(count); // => undefined(同樣被提升)
var count = 10;
}
demo();
註解:
var message在執行前已被提升,但賦值仍在原本位置,故第一次console.log只會得到undefined。同理,函式內的var count只在demo函式內有效。
範例 2:全域汙染
function setGlobal() {
// 沒有使用 var/let/const,直接賦值會自動成為全域變數
accidental = 42;
}
setGlobal();
console.log(accidental); // => 42(全域變數)
警告:在嚴格模式 (
'use strict') 下,未宣告直接賦值會拋出ReferenceError,但在非嚴格模式下會產生隱式全域變數,容易造成 全域汙染。
2. let – 块級作用域(block scope)
let 是 ES6(ECMAScript 2015)引入的關鍵字,解決了 var 在作用域與提升上的缺陷。
| 特性 | 說明 |
|---|---|
| 作用域 | 块級作用域({} 內部),包括 if、for、while、函式等 |
| 提升 | 仍會提升,但在宣告之前存取會拋出 ReferenceError(稱為「暫時性死區」Temporal Dead Zone) |
| 可重新賦值 | 可以改變值,但不能重新宣告同名變數於同一作用域內 |
範例 3:let 的块級作用域
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ReferenceError: i is not defined
說明:
i僅在for迴圈的块級作用域內有效,迴圈結束後即不存在。
範例 4:暫時性死區 (TDZ)
{
// console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;
console.log(x); // 5
}
重點:即使變數已被提升,仍必須在宣告之後才能存取,避免了
var那種「提前使用卻得到undefined」的情況。
3. const – 常數宣告
const 同樣具備 块級作用域,但它的值一旦設定就不可再更改(不可重新賦值)。值得注意的是,const 只保證「變數指向的記憶體位址」不可變,若指向的是物件或陣列,物件的屬性仍可變更。
| 特性 | 說明 |
|---|---|
| 作用域 | 块級作用域 |
| 提升 | 同 let,存在暫時性死區 |
| 不可重新賦值 | 必須在宣告時初始化,之後不能改變指向 |
| 物件/陣列的可變性 | 只保證指向不變,內容仍可變更(除非使用 Object.freeze) |
範例 5:const 與物件的可變性
const USER = { name: 'Alice', age: 25 };
USER.age = 26; // ✅ 可改變屬性
// USER = { name: 'Bob' }; // TypeError: Assignment to constant variable.
範例 6:const 必須立即初始化
// const PI; // SyntaxError: Missing initializer in const declaration
const PI = 3.14159;
console.log(PI);
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
使用 var 造成全域汙染 |
在大型專案中容易不小心寫成全域變數,導致衝突 | 盡量避免 var,改用 let/const |
| 重新宣告同名變數 | var 允許在同一作用域內多次宣告,會覆蓋先前的值 |
使用 let/const,編譯器會報錯提醒 |
忘記初始化 const |
const 必須在宣告時賦值,否則會拋 SyntaxError |
立即賦值,或改用 let |
誤以為 const 讓物件不可變 |
只保證指向不變,物件內容仍可被修改 | 若需要真正的不可變物件,使用 Object.freeze() 或 Immutable.js 等工具 |
| TDZ(暫時性死區)誤用 | 在宣告之前使用 let/const 會拋 ReferenceError |
確保變數在使用前已宣告,或將宣告提升至程式碼最前面 |
推薦的變數宣告策略
預設使用
const- 大多數情況下,變數不需要重新指向,使用
const能讓程式更具可預測性。
- 大多數情況下,變數不需要重新指向,使用
當需要重新賦值時改用
let- 迴圈計數器、暫存值、或根據條件改變的變數適合使用
let。
- 迴圈計數器、暫存值、或根據條件改變的變數適合使用
只有在必須兼容舊環境或特殊需求時使用
var- 例如在老舊瀏覽器或某些第三方庫中仍需要
var,此時要特別留意作用域與提升的行為。
- 例如在老舊瀏覽器或某些第三方庫中仍需要
實際應用場景
1. 表單驗證的暫存值(使用 let)
在處理使用者輸入時,常需要在同一函式內多次更新暫存值:
function validateForm(data) {
let isValid = true; // 需要在過程中改變
let errorMsg = '';
if (!data.email.includes('@')) {
isValid = false;
errorMsg = 'Email 格式不正確';
}
if (data.password.length < 8) {
isValid = false;
errorMsg = '密碼長度至少 8 位';
}
return { isValid, errorMsg };
}
2. 配置常數(使用 const)
全域或模組層級的設定值應該固定不變:
// config.js
export const API_ENDPOINT = 'https://api.example.com/v1';
export const TIMEOUT_MS = 5000;
export const SUPPORTED_LANGUAGES = Object.freeze(['zh-TW', 'en', 'ja']);
3. 動態生成 UI 元素(混合使用 let 與 const)
function renderList(items) {
const ul = document.createElement('ul'); // 不會變更指向
for (let i = 0; i < items.length; i++) {
const li = document.createElement('li'); // 每次迭代都重新建立
li.textContent = items[i];
ul.appendChild(li);
}
document.body.appendChild(ul);
}
4. 舊版程式碼遷移(逐步替換 var)
// 舊版
var total = 0;
for (var i = 0; i < data.length; i++) {
total += data[i];
}
console.log(total);
// 改寫後
let total = 0;
for (let i = 0; i < data.length; i++) {
total += data[i];
}
console.log(total);
透過上述改寫,我們消除了 i 在迴圈外仍可被存取的風險,提升了程式的可讀性與安全性。
總結
var:具函式作用域,會提升且允許重新宣告與重新賦值。除非有特殊相容需求,否則應盡量避免使用。let:具块級作用域,提升但受暫時性死區保護,可重新賦值但不可重新宣告。適合需要在同一作用域內變更值的情況。const:同let的块級作用域與提升機制,但必須在宣告時初始化且指向不可變。適合所有不需要重新指向的變數,讓程式更具可預測性。
在日常開發中,我們 推薦先使用 const,若確實需要變更值,再改用 let,最後僅在必須支援舊環境或特殊情境時才考慮 var。透過正確的變數宣告方式,我們不僅能降低 bug 的發生率,還能讓程式碼更易於維護與閱讀。祝你在 JavaScript 的世界裡寫出乾淨、可靠的程式!