本文 AI 產出,尚未審核
TypeScript – 陣列與集合(Arrays & Collections)
主題:Map / Set / WeakMap / WeakSet 型別
簡介
在日常開發中,我們常會需要 快速查找、去除重複 或 管理物件之間的關聯。
ES6 之後,JavaScript(亦即 TypeScript)提供了 Map、Set、WeakMap、WeakSet 四種集合型別,讓這些需求變得更直觀且效能更佳。
對於 TypeScript 使用者而言,這些集合不僅是純粹的資料結構,更能透過 型別系統 把錯誤提前捕捉,提升程式碼的可讀性與安全性。了解它們的特性、使用時機與常見陷阱,是從「陣列」升級到「集合」的關鍵一步。
核心概念
1. Map – 鍵值對的有序集合
特點
- 任意類型(原始值、物件、函式)皆可作為 key。
- 插入順序會被保留,
for...of可直接遍歷。 size屬性即時回傳元素數量。
常見方法
| 方法 | 說明 |
|---|---|
set(key, value) |
新增或更新鍵值對 |
get(key) |
取得對應的值,找不到回傳 undefined |
has(key) |
判斷鍵是否存在 |
delete(key) |
移除指定鍵 |
clear() |
清空所有鍵值對 |
程式碼範例 1:基本操作
const userRoles = new Map<string, string>();
// 新增
userRoles.set('alice', 'admin');
userRoles.set('bob', 'editor');
// 取得
console.log(userRoles.get('alice')); // => 'admin'
// 判斷
if (userRoles.has('charlie')) {
console.log('found');
} else {
console.log('not found'); // => not found
}
// 迭代
for (const [name, role] of userRoles) {
console.log(`${name}: ${role}`);
}
程式碼範例 2:使用物件作為 key
type Point = { x: number; y: number };
const pointMap = new Map<Point, string>();
const p1: Point = { x: 0, y: 0 };
const p2: Point = { x: 1, y: 1 };
pointMap.set(p1, 'origin');
pointMap.set(p2, 'diagonal');
console.log(pointMap.get(p1)); // => 'origin'
注意:即使
p1與p2的屬性相同,只要參考不同,就會被視為不同的 key。
2. Set – 唯一值的集合
特點
- 只允許 唯一(
===)的值,重複的元素會自動被忽略。 - 同樣支援任意類型作為元素。
- 具備
size屬性與for...of迭代。
- 只允許 唯一(
常見方法
| 方法 | 說明 |
|---|---|
add(value) |
新增元素(若已存在則不變) |
has(value) |
判斷元素是否存在 |
delete(value) |
移除指定元素 |
clear() |
清空集合 |
程式碼範例 3:去除陣列重複值
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = new Set<number>(numbers);
console.log([...uniqueNumbers]); // => [1, 2, 3, 4, 5]
程式碼範例 4:集合運算(交集、聯集)
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);
// 聯集
const union = new Set([...setA, ...setB]);
console.log(union); // Set { 1, 2, 3, 4, 5 }
// 交集
const intersect = new Set([...setA].filter(x => setB.has(x)));
console.log(intersect); // Set { 3 }
3. WeakMap – 允許垃圾回收的鍵值對
特點
- Key 必須是物件(不可為原始值)。
- 只保留 弱引用,當 key 物件不再被其他變數引用時,對應的條目會自動被回收。
- 沒有
size、clear(),只能使用set、get、has、delete。
適用情境
- 為物件「附加」額外資訊,但不想影響其生命週期(例如緩存、私有屬性)。
程式碼範例 5:實作私有屬性
const privateData = new WeakMap<object, { secret: string }>();
class Person {
constructor(public name: string, secret: string) {
// 只把 secret 存在 WeakMap,外部無法直接取得
privateData.set(this, { secret });
}
revealSecret() {
return privateData.get(this)!.secret;
}
}
const alice = new Person('Alice', 'I love TypeScript');
console.log(alice.revealSecret()); // => I love TypeScript
// 當 alice 不再被引用,privateData 中的條目會自動被 GC 回收
4. WeakSet – 僅接受物件且允許垃圾回收的集合
- 特點
- 只能存放物件,且以弱引用方式持有。
- 沒有
size、clear(),僅支援add、has、delete。 - 常用於 判斷物件是否已被處理過,而不希望因集合本身阻止 GC。
程式碼範例 6:避免重複執行
const processed = new WeakSet<object>();
function handle(node: object) {
if (processed.has(node)) {
return; // 已處理過,直接返回
}
// ... 進行處理
processed.add(node);
}
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
誤以為 Map 能直接使用陣列作 key |
map.set([1,2], 'value') 與 map.set([1,2], 'other') 會被視為不同的 key,因為陣列是不同的參考。 |
若需要根據內容比較,請先 序列化(JSON.stringify)或自行實作哈希函式。 |
忘記 WeakMap/WeakSet 只能接受物件 |
把字串或數字傳入會拋出 TypeError。 | 確認 key 或元素為 物件,若需要儲存原始值,請改用 Map/Set。 |
使用 WeakMap 時期待 size |
WeakMap 沒有 size,無法直接統計條目數量。 |
只在需要 自動回收 時使用;若需要計數,請自行維護計數器或改用 Map。 |
在迭代 Set 時修改集合 |
在 for...of 迴圈內 add/delete 可能導致意外行為。 |
盡量 先複製([...mySet])再迭代,或使用 forEach。 |
忘記 Map 的插入順序 |
有時開發者誤以為 Map 會自動排序。 |
若需要排序,請在迭代前 轉成陣列 並使用 sort。 |
最佳實踐
- 型別註記:利用 TypeScript 的泛型明確指定鍵和值的型別,例如
Map<string, User>,可在編譯期捕捉錯誤。 - 避免過度使用:對於僅需「去重」的情況,
Set已足夠;若僅需「快速查找」且鍵是字串/數字,Object或Record仍是輕量選擇。 - 記憶體管理:若集合內的元素可能長期保留且不再使用,考慮改用
WeakMap/WeakSet,讓 GC 能回收。 - 統一 API:在大型專案中,封裝
Map/Set操作(如addIfAbsent、getOrDefault)可以提升可讀性與一致性。
實際應用場景
| 場景 | 建議集合 | 為什麼選擇 |
|---|---|---|
| 使用者權限管理 | Map<string, Role[]> |
需要根據使用者 ID 快速查找多個角色,且插入順序不重要。 |
| 即時聊天訊息去重 | Set<string>(訊息 ID) |
訊息 ID 為唯一字串,使用 Set 可防止重複顯示。 |
| DOM 元素快取 | WeakMap<Element, CacheData> |
元素被移除時自動釋放快取,避免記憶體洩漏。 |
| 事件監聽器的已註冊檢查 | WeakSet<Function> |
只要函式物件仍在使用,集合會保留;函式被垃圾回收時自動清除。 |
| 圖形演算法中的鄰接表 | Map<Node, Set<Node>> |
每個節點對應一組相鄰節點,Set 確保不會重複連線。 |
總結
Map、Set、WeakMap、WeakSet 是 現代 JavaScript/TypeScript 中不可或缺的集合工具。
Map提供 任意類型鍵值對 且保持插入順序,適合需要快速查找與遍歷的情境。Set以 唯一值 為核心,常用於去重與集合運算。WeakMap與WeakSet以 弱引用 為特點,能在不干擾垃圾回收的前提下,為物件附加額外資訊或標記。
掌握它們的特性、正確的型別宣告以及避免常見陷阱,就能在 效能、記憶體管理與程式可讀性 三方面都取得顯著提升。未來在開發大型前端或 Node.js 應用時,善用這四種集合將是提升程式品質的關鍵利器。祝你寫程式順利、寫出更乾淨的 TypeScript 代碼!