HOWTO · JavaScript
JavaScript 元組替代方案:陣列、物件與 TypeScript
JavaScript 沒有原生元組型別。依資料契約選擇陣列、物件、Object.freeze()、解構賦值或 TypeScript 元組。
本頁內容
JavaScript 在執行階段沒有原生元組型別。對於座標這類簡短且順序固定的資料,可以使用陣列並透過解構賦值讀取。欄位名稱更能說明資料時,應使用物件。需要在執行階段阻止直接修改時,可以凍結陣列;需要在開發階段檢查固定長度和元素型別時,則使用 TypeScript 元組。
使用陣列表示有序值
當每個位置都有穩定含義時,陣列可以作為類似元組的值。以下函式依最小值、最大值的順序傳回結果:
function minMax(values) {
return [Math.min(...values), Math.max(...values)];
}
const [minimum, maximum] = minMax([7, 3, 12]);
console.log(minimum, maximum);
輸出:
3 12
解構賦值為各個位置提供清楚的區域變數名稱。請為呼叫端說明傳回順序。JavaScript 不會強制陣列長度或元素型別,因此外部資料在作為位置契約使用前必須先驗證。
僅在需要執行階段保護時凍結陣列
const 只會阻止變數重新賦值,不會阻止修改變數所參照的陣列。Object.freeze() 凍結的是陣列本身:
"use strict";
const point = Object.freeze([48.8566, 2.3522]);
console.log(point[0], point[1], Object.isFrozen(point));
// point[0] = 0; // TypeError in strict mode
// point.push(3); // TypeError
輸出:
48.8566 2.3522 true
Object.freeze() 會傳回原本的同一個物件,不會建立副本。如果呼叫端仍擁有輸入陣列,應先複製再凍結,以免意外限制呼叫端對原陣列的操作。
function asFrozenPoint(value) {
if (
!Array.isArray(value) ||
value.length !== 2 ||
!value.every(Number.isFinite)
) {
throw new TypeError("Expected two finite coordinates");
}
return Object.freeze([...value]);
}
凍結是淺層的。它會阻止修改陣列的直接元素、長度和自身屬性,但陣列元素中的巢狀物件仍然可以修改:
const settings = Object.freeze([{ theme: "light" }]);
settings[0].theme = "dark";
console.log(settings[0].theme);
輸出:
dark
因此,不應將淺層凍結的巢狀結構稱為完全不可變。深度凍結必須遞迴走訪巢狀值並處理循環參照,只有確實需要這項保證時才值得實作。
名稱比位置更清楚時使用物件
當一個值包含多個字串、布林值、選用欄位或將來可能增加的欄位時,只靠位置很容易造成誤解:
function createUser() {
return { id: 42, name: "Ada", active: true };
}
const { id, name, active } = createUser();
陣列適合 [x, y] 或 Object.entries() 傳回的 [key, value] 這類緊湊且含義明確的組合。如果呼叫端必須記住索引 1 或 2 的意義,具有命名屬性的物件通常更安全。
Object.entries() 已經提供有文件說明的位置組合。透過解構賦值,可以在使用處明確表示這兩個位置:
for (const [key, value] of Object.entries({ theme: "light" })) {
console.log(`${key}: ${value}`);
}
輸出:
theme: light
使用 TypeScript 檢查元組契約
TypeScript 可以表達陣列的長度、元素順序和元素型別:
const point: readonly [number, number] = [48.8566, 2.3522];
readonly 會在 TypeScript 原始碼中阻止寫入。編譯後,該值仍是普通 JavaScript 陣列,不會自動在執行階段凍結。只有同時需要靜態型別檢查和執行階段保護時,才需要將 TypeScript 元組與 Object.freeze() 搭配使用。
摘要
JavaScript 陣列可以依慣例作為元組使用,並透過解構賦值讀取。命名欄位請選擇物件,淺層執行階段保護請選擇 Object.freeze(),固定長度和型別的靜態檢查請選擇 TypeScript 元組。