📋 目錄
這是 TC39 新語法深度解析系列的第三篇。過去十年,我們處理 Set 之間的集合運算(合併、交叉、差集)需要繞路:用
spread + filter或第三方工具函式。ES2024 後,七個新的 Set 方法讓這一切變成一行。
過去的做法:繞路的痛苦
const admins = new Set(['alice', 'bob', 'charlie']);
const moderators = new Set(['bob', 'charlie', 'david']);
// 過去合併兩個 Set(union)
const allModerators = new Set([...admins, ...moderators]);
// ['alice', 'bob', 'charlie', 'david']
// 過去交集(intersection)
const modsWhoAreAdmins = new Set(
[...admins].filter(x => moderators.has(x))
); // ['bob', 'charlie']
// 過去差集(admins 減去 moderators)
const adminsOnly = new Set(
[...admins].filter(x => !moderators.has(x))
); // ['alice']
這些 .filter() + .has() 的模式,到處重複——終於可以退休了。
七個新方法速覽
1. union(other):合併
const admins = new Set(['alice', 'bob', 'charlie']);
const moderators = new Set(['bob', 'charlie', 'david']);
const allStaff = admins.union(moderators);
console.log([...allStaff]); // ['alice', 'bob', 'charlie', 'david']
2. intersection(other):交集
const modsWhoAreAdmins = moderators.intersection(admins);
console.log([...modsWhoAreAdmins]); // ['bob', 'charlie']
3. difference(other):差集
const adminsOnly = admins.difference(moderators);
console.log([...adminsOnly]); // ['alice']
4. symmetricDifference(other):對稱差集(只在其中一個集合的元素)
const exclusiveToOne = admins.symmetricDifference(moderators);
console.log([...exclusiveToOne]); // ['alice', 'david']
唯讀查詢方法
// 5. isSubsetOf(other):是否為子集
const teamLeads = new Set(['alice']);
console.log(teamLeads.isSubsetOf(admins)); // true
// 6. isSupersetOf(other):是否為父集
console.log(admins.isSupersetOf(teamLeads)); // true
// 7. isDisjointFrom(other):是否無交集
const contractors = new Set(['eve', 'frank']);
console.log(contractors.isDisjointFrom(admins)); // true(無交集)
實際應用場景
權限系統
// 使用者的所有有效權限
function getEffectivePermissions(user) {
const direct = new Set(user.directPermissions);
const fromRoles = user.roles.flatMap(role => role.permissions);
return fromRoles.reduce(
(acc, perm) => acc.union(new Set([perm])),
new Set(direct)
);
}
// 檢查是否有某權限
const perms = getEffectivePermissions(currentUser);
console.log(perms.has('admin:delete')); // true / false
// 檢查是否有一籃子權限(全都有)
const required = new Set(['posts:read', 'posts:write']);
console.log(required.isSubsetOf(perms)); // true(通過)
標籤過濾
// 文章的所有標籤
const article1Tags = new Set(['javascript', 'typescript', 'performance']);
const article2Tags = new Set(['typescript', 'react', 'css']);
// 共同標籤(推薦相關文章)
const relatedTags = article1Tags.intersection(article2Tags);
console.log([...relatedTags]); // ['typescript']
// 排除已讀標籤後的推薦
const readTags = new Set(['javascript']);
const unreadRelatedTags = article2Tags.difference(readTags);
console.log([...unreadRelatedTags]); // ['typescript', 'react', 'css']
特徵標誌(Feature Flags)
const enabledFlags = new Set(['new-checkout', 'dark-mode', 'beta-search']);
const userOverrides = new Set(['new-checkout:false', 'beta-search:false']);
function getEffectiveFlags(enabled, overrides) {
const disabled = new Set(
[...overrides]
.filter(f => f.endsWith(':false'))
.map(f => f.replace(':false', ''))
);
const enabledOnes = enabled.difference(disabled);
return enabledOnes;
}
console.log([...getEffectiveFlags(enabledFlags, userOverrides)]);
// ['dark-mode'](被禁用的兩個已移除)
注意:isSubsetOf 等方法要求 Set 本身
// 這些唯讀方法是定義在 Set.prototype 上的
// 它們接受任何有 .size 和 .has() 的物件
const fakeSet = {
size: 3,
has: (x) => x === 'a'
};
// new Set(['a', 'b', 'c']).isSubsetOf(fakeSet)
// 會呼叫 fakeSet.size 和 fakeSet.has()
// 這就是 Set Protocol——可以用於任何「類 Set」物件
瀏覽器支援
ES2024(即所有主流瀏覽器 2024 年版本)都已支援:
| 瀏覽器 | 支援版本 |
|---|---|
| Chrome | 122+(2024年3月) |
| Firefox | 127+(2024年7月) |
| Safari | 17.4+(2024年3月) |
| Node.js | 22.0+ |
可以直接在產品環境使用,無需 polyfill。
總結
Set 新方法是過去幾年 TC39 最受歡迎的提案之一——它解決的是我們每天都在面對的問題,只是以前沒有乾淨的原生解法。
// 從此告別這種:
[...set1].filter(x => set2.has(x))
// 直接用:
set1.intersection(set2)
這是 TC39 新語法深度解析系列的第三篇。下一篇:Promise.withResolvers — 更優雅地建立可控 Promise。
延伸閱讀
- TypeScript 6.0 RC 升級指南 — 支援 Set 新方法
本文基於 TC39 Set Methods 提案(Stage 4,ES2024)整理。