本文 AI 產出,尚未審核

ES6+ 新特性:letconst 完全攻略


簡介

在 ES6(ECMAScript 2015)正式推出之前,JavaScript 只提供了 var 這一種宣告變數的方式。var 的作用域(scope)是 函式層級,且會因為 變數提升(hoisting) 而產生許多令人困惑的行為。
隨著前端框架與大型應用程式的興起,開發者對程式碼可讀性、可維護性與錯誤防護的需求日益提升。為了讓變數的生命週期更直觀、避免不小心重新賦值,ECMAScript 2015 引入了兩個全新的關鍵字:letconst

本篇文章將從概念、語法、實作範例、常見陷阱與最佳實踐,完整說明 let / const 的使用方式,幫助 初學者 迅速上手,也讓 中階開發者 能在實務上更安心地寫出安全、易維護的程式碼。


核心概念

1. 作用域(Scope)—— 由 函式層級 變成 區塊層級

關鍵字 作用域類型
var 函式層級(function scope)
let / const 區塊層級(block scope)

區塊層級 指的是被 {} 包住的程式區段,例如 ifforwhiletry...catch,甚至是單純的程式區塊。

範例 1:varlet 的作用域差異

function demoVar() {
  if (true) {
    var a = 10;   // a 仍屬於函式作用域
  }
  console.log(a); // 10
}

function demoLet() {
  if (true) {
    let b = 20;   // b 只在 if 區塊內有效
  }
  // console.log(b); // ReferenceError: b is not defined
}
demoVar();
demoLet();
  • var 會把變數提升至函式最上方,導致在區塊外仍可存取。
  • let(以及 const)只在宣告所在的區塊內可見,提升(hoisting)仍會發生,但在宣告之前存取會拋出 ReferenceError(所謂的 TDZ,Temporal Dead Zone)。

2. 常數(Constant)—— const 讓值「不可重新指派」

const 並不代表「不可變」,而是 變數名稱不能再指向其他值。如果值本身是物件或陣列,仍然可以修改其內部屬性或元素。

範例 2:const 與物件/陣列

const PI = 3.14159;        // 正確:宣告常數
// PI = 3;                 // TypeError: Assignment to constant variable.

const config = { api: '/v1' };
config.api = '/v2';        // ✅ 仍可修改屬性
// config = {};            // TypeError

const list = [1, 2, 3];
list.push(4);              // ✅ 仍可變更內容
// list = [];              // TypeError

重點const 只保證「變數指向」不變,若需要真正不可變的資料結構,可使用 Object.freeze() 或第三方函式庫(如 Immutable.js)。

3. 暫時性死區(Temporal Dead Zone, TDZ)

即使 let / const 會被提升,但在宣告之前存取會拋出錯誤,這段區域稱為 TDZ。它避免了因變數提升而產生的意外行為,讓程式碼更易預測。

範例 3:TDZ 示範

console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;

4. 重複宣告的限制

  • 同一區塊內 不能 同時使用 let / const 重新宣告同名變數。
  • var 可以重複宣告,這在大型程式碼中容易造成衝突。
let foo = 1;
// let foo = 2; // SyntaxError: Identifier 'foo' has already been declared
var bar = 1;
var bar = 2; // ✅ 允許(但不建議)

程式碼範例(實用示例)

以下提供 5 個 常見情境的範例,說明在實務開發中如何正確使用 letconst

範例 1:迴圈內部的暫時變數

// 使用 let 讓每一次迭代都有自己的 i
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 0, 1, 2
}

// 若改用 var,所有回呼都會印出 3
for (var j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100); // 3, 3, 3
}

實務建議:在 forfor...offor...in 等迴圈中,盡量使用 let,避免因閉包(closure)而產生的錯誤。


範例 2:函式參數的預設值與解構賦值

function createUser({ name = 'Anonymous', age = 0 } = {}) {
  // name、age 為 const(不可重新指派)
  const user = { name, age };
  return user;
}

console.log(createUser({ name: 'Alice', age: 25 })); // { name: 'Alice', age: 25 }
console.log(createUser()); // { name: 'Anonymous', age: 0 }
  • 使用 解構賦值 時,預設值可以讓函式更具彈性。
  • 內部 user 使用 const,保證不會被意外改寫。

範例 3:避免全域汙染(IIFE + block)

// 傳統寫法:使用 IIFE(Immediately Invoked Function Expression)防止全域變數
(function () {
  var temp = 'temp';
  console.log(temp);
})();

// ES6 寫法:直接利用區塊作用域
{
  let temp = 'temp';
  console.log(temp);
}
// console.log(temp); // ReferenceError

重點let / const 讓我們不再需要額外的 IIFE,程式碼更簡潔。


範例 4:深層物件的不可變(Object.freeze)

const SETTINGS = Object.freeze({
  API_ENDPOINT: 'https://api.example.com',
  TIMEOUT: 5000,
});

// 嘗試修改會失敗(在嚴格模式下會拋錯)
// SETTINGS.API_ENDPOINT = 'https://evil.com'; // TypeError in strict mode
  • Object.freeze 結合 const,可以建立真正的 只讀設定,在大型專案中相當常見。

範例 5:條件式區塊中的變數宣告

function calculate(value) {
  if (value > 0) {
    const result = Math.sqrt(value);
    return result;
  } else {
    // const result = Math.abs(value); // SyntaxError: Identifier 'result' has already been declared
    let result = Math.abs(value);
    return result;
  }
}
  • const 只能在單一區塊內宣告一次,若需要在不同分支使用相同名稱,請改用 let 或在外層先宣告。

常見陷阱與最佳實踐

陷阱 說明 解決方式
TDZ 被忽略 在宣告前存取 let/const 會拋錯。 確保變數宣告在使用之前,或使用 var(不建議)。
誤以為 const = immutable 只保證「指向」不變,物件仍可被修改。 需要不可變時使用 Object.freezedeepFreeze 或 Immutable.js。
在迴圈外誤用 var 產生意料之外的共享變數。 迴圈內部盡量使用 let,若需要外層值則使用 const
重複宣告 同一區塊內 let/const 重複宣告會錯誤。 檢查變數命名或使用不同作用域。
全域污染 使用 var 宣告全域變數,容易被其他腳本覆寫。 預設使用 let/const,若必須全域,明確放在 window(或 globalThis)上。

最佳實踐清單

  1. 預設使用 const:除非明確需要重新指派,否則一律以 const 宣告。
  2. 僅在需要時使用 let:例如迴圈變數、條件式分支需要重新賦值的情況。
  3. 避免在同一作用域內混用 var:若必須保留舊程式碼,先行轉換為 let/const
  4. 利用 ESLint 之 prefer-constno-var 規則:自動檢測不當使用。
  5. 在大型物件或設定檔上使用 Object.freeze:保證不被意外改寫。

實際應用場景

1. 前端框架(React / Vue)中的 State 管理

React 函式元件常使用 useStateuseReducer,內部狀態變數必須是 不可變 的。

const [count, setCount] = useState(0); // count 為 const
// 更新時不直接改寫,而是呼叫 setCount()

2. Node.js 後端服務的設定檔

// config.js
export const CONFIG = Object.freeze({
  PORT: 3000,
  DB_URI: process.env.DB_URI,
});

其他模組只要 import { CONFIG } from './config';,就能保證設定不會被改寫。

3. 測試環境中的 Mock 變數

在單元測試時,常需要暫時改寫全域變數。使用 let 可以在 beforeEach 中重新指派,測試結束後再恢復。

let fetchData;

beforeEach(() => {
  fetchData = jest.fn().mockResolvedValue({ success: true });
});

test('should call fetchData', async () => {
  await myModule.doSomething(fetchData);
  expect(fetchData).toHaveBeenCalled();
});

4. 迭代演算法與資料處理

在資料清洗、排序等演算法裡,使用 let 來儲存暫時的索引或暫存值,確保每一次迭代不會相互干擾。

function bubbleSort(arr) {
  const n = arr.length; // 不會改變
  for (let i = 0; i < n - 1; i++) {
    for (let j = 0; j < n - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        // 交換
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}

總結

  • letconst變數的作用域 從函式層級升級為 區塊層級,減少意外共享與提升可讀性。
  • const 保證指向不變,但若值是物件或陣列,仍可修改其內容;需要真正不可變時,可結合 Object.freeze
  • TDZ(暫時性死區)是 let/const 的安全機制,提醒開發者在宣告之前不要使用變數。
  • 在實務開發中,預設使用 const,只有在需要重新指派時才使用 let,並盡量避免 var
  • 透過 ESLint、測試與程式碼審查,能夠養成正確使用 let / const 的好習慣,提升專案的可維護性與穩定性。

掌握了這些概念與實作技巧後,你將能在 Modern JavaScript 的開發環境裡,寫出更安全、更具可讀性的程式碼。祝你寫程式愉快,持續探索 ES6+ 的更多新特性! 🚀