本文 AI 產出,尚未審核

TypeScript – 陣列與集合(Arrays & Collections)

主題:Map / Set / WeakMap / WeakSet 型別


簡介

在日常開發中,我們常會需要 快速查找去除重複管理物件之間的關聯
ES6 之後,JavaScript(亦即 TypeScript)提供了 MapSetWeakMapWeakSet 四種集合型別,讓這些需求變得更直觀且效能更佳。

對於 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'

注意:即使 p1p2 的屬性相同,只要參考不同,就會被視為不同的 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 物件不再被其他變數引用時,對應的條目會自動被回收。
    • 沒有 sizeclear(),只能使用 setgethasdelete
  • 適用情境

    • 為物件「附加」額外資訊,但不想影響其生命週期(例如緩存、私有屬性)。

程式碼範例 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 – 僅接受物件且允許垃圾回收的集合

  • 特點
    • 只能存放物件,且以弱引用方式持有。
    • 沒有 sizeclear(),僅支援 addhasdelete
    • 常用於 判斷物件是否已被處理過,而不希望因集合本身阻止 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

最佳實踐

  1. 型別註記:利用 TypeScript 的泛型明確指定鍵和值的型別,例如 Map<string, User>,可在編譯期捕捉錯誤。
  2. 避免過度使用:對於僅需「去重」的情況,Set 已足夠;若僅需「快速查找」且鍵是字串/數字,ObjectRecord 仍是輕量選擇。
  3. 記憶體管理:若集合內的元素可能長期保留且不再使用,考慮改用 WeakMap/WeakSet,讓 GC 能回收。
  4. 統一 API:在大型專案中,封裝 Map/Set 操作(如 addIfAbsentgetOrDefault)可以提升可讀性與一致性。

實際應用場景

場景 建議集合 為什麼選擇
使用者權限管理 Map<string, Role[]> 需要根據使用者 ID 快速查找多個角色,且插入順序不重要。
即時聊天訊息去重 Set<string>(訊息 ID) 訊息 ID 為唯一字串,使用 Set 可防止重複顯示。
DOM 元素快取 WeakMap<Element, CacheData> 元素被移除時自動釋放快取,避免記憶體洩漏。
事件監聽器的已註冊檢查 WeakSet<Function> 只要函式物件仍在使用,集合會保留;函式被垃圾回收時自動清除。
圖形演算法中的鄰接表 Map<Node, Set<Node>> 每個節點對應一組相鄰節點,Set 確保不會重複連線。

總結

MapSetWeakMapWeakSet現代 JavaScript/TypeScript 中不可或缺的集合工具。

  • Map 提供 任意類型鍵值對 且保持插入順序,適合需要快速查找與遍歷的情境。
  • Set唯一值 為核心,常用於去重與集合運算。
  • WeakMapWeakSet弱引用 為特點,能在不干擾垃圾回收的前提下,為物件附加額外資訊或標記。

掌握它們的特性、正確的型別宣告以及避免常見陷阱,就能在 效能、記憶體管理與程式可讀性 三方面都取得顯著提升。未來在開發大型前端或 Node.js 應用時,善用這四種集合將是提升程式品質的關鍵利器。祝你寫程式順利、寫出更乾淨的 TypeScript 代碼!