前端進階
-JS Symbol 是什麼?5 分鐘詳解與實戰應用
this.web

Symbol 是 ES6 引入的 JS 原始型別,他專門用來建立唯一且不可重複的識別符號(unique identifier)。
Symbol 很適合用在需要不重複鍵名與封裝屬性的場景。這篇文章就會從頭帶你理解 Symbol 的語法以及實戰應用。
Symbol 基本用法
用法很簡單,直接呼叫 Symbol() 就好,每次呼叫 Symbol() 都會得到全新且不相等的值,這是它的核心特性。
let sym1 = Symbol();
let sym2 = Symbol();
console.log(sym1 === sym2); // false可以看到上面程式碼的比較會是 false。
除此之外,我們也可以加入對於 Symbol 的描述,用來增加可讀性和除錯,不影響唯一性
let id = Symbol('userId');
console.log(id); // Symbol(userId)
console.log(Symbol('userId') === Symbol('userId')); // false我們也可以直接使用 symbol.description 來直接獲取描述
let id = Symbol('userId');
console.log(id.description); // userId在物件中用 Symbol 當屬性鍵
由於每一個 Symbol 值都是不相等的,這代表只要我們用 Symbol 值作為物件的屬性,就能保證不會出現同名的屬性。
這樣可以避免命名衝突,也能防止被意外存取、覆蓋或枚舉到,比如說:
const idKey = Symbol('id');
const user = {
[idKey]: 123,
name: 'Alice',
role: 'Admin',
};
console.log(user.idKey); // undefined
// 因為 . 後面屬於字符串,所以不會讀取到 idKey 這個 Symbol
console.log(user[idKey]); // 123
// 不會被枚舉到
console.log(Object.keys(user)); // ['name', 'role']
console.log(JSON.stringify(user)); // {"name":"Alice","role":"Admin"}當你需要在外部套件或共用物件上「安全地」擴充欄位時,用 Symbol 可避免覆蓋原本屬性。
const uniqueToken = Symbol('token');
let session: any = { id: 1001, user: 'Bob' };
session[uniqueToken] = 'secret-token-xyz';
// 不會出現在 for...in / Object.keys() / JSON.stringify()全域註冊應用:Symbol.for() 與 Symbol.keyFor()
Symbol.for() 和 Symbol.keyFor() 可以讓我們跨模組地共用同一個 Symbol。
我們可以用 Symbol.for() 來宣告一個全局的 Symbol
console.log(Symbol.for('app') === Symbol.for('app')); // true
console.log(Symbol('app') === Symbol('app')); // false並使用 Symbol.keyFor() 來反為對應的全局 Symbol
const s = Symbol.for('app');
console.log(Symbol.keyFor(s)); // 'app'Symbol 與 TypeScript 結合:symbol 與 unique symbol
我們可以使用 symbol 當作 type
let s: symbol = Symbol('symbol');一般 symbol 的限制
但一般的 symbol type 也有一些限制,比如以下程式碼:
const A = Symbol('A');
const B = Symbol('B');
type Shape = typeof A | typeof B;這裡的 typeof A 和 typeof B 都只是 symbol 型別。
在 TypeScript 看來,它們沒有差別,都是某個 symbol。
使用 unique symbol 的好處
所以我們可以使用 unique symbol:
const TRIANGLE: unique symbol = Symbol('triangle');
const SQUARE: unique symbol = Symbol('square');
const CIRCLE: unique symbol = Symbol('circle');這樣宣告後,typeof TRIANGLE、typeof SQUARE、typeof CIRCLE 都是獨立的型別。
於是就可以用:
type Shape = typeof TRIANGLE | typeof SQUARE | typeof CIRCLE;這樣 TS 編譯器知道:
TRIANGLE、SQUARE、CIRCLE各自是不同型別。Shape是一個 union,可以在switch、if自動偵測到他的 type。類似 enum 的用法。
function draw(shape: Shape) {
if (shape === TRIANGLE || shape === SQUARE || shape === CIRCLE) {
//...
}
}實用 Symbol 應用:作為私有鍵(較不易被誤用的欄位)
當你寫第三方套件、要在使用者的物件上加一些內部資訊時,為了避免覆蓋到人家的 props 或 key,就可以使用 Symbol() 產生鍵,因為值是獨一無二的,即便別人也用了同樣的字面名稱,兩個符號仍然不同
而且符號屬性預設不會被列舉,等於把內部欄位「藏」起來,不會和使用者的資料互相干擾。這樣就能安全地在物件上維護內部狀態或標記等等
const INTERNAL_META = Symbol('myLib.internalMeta');
function attachMeta(target, info) {
target[INTERNAL_META] = {
info,
timestamp: Date.now(),
};
}
function readMeta(target) {
const meta = target[INTERNAL_META];
return meta && typeof meta === 'object' ? meta.info : null;
}
// 使用者物件
const userProps = { name: 'Alice', role: 'admin' };
attachMeta(userProps, 'My Info');
console.log(Object.keys(userProps)); // ['name', 'role'] → 不會看到符號欄位
console.log(readMeta(userProps)); // 'My Info'
userProps.INTERNAL_META = 'abc'; // 不會影響到 Symbol
console.log(userProps.INTERNAL_META); // 'abc'
console.log(readMeta(userProps)); // 'My Info'總結
這篇文章介紹了 JavaScript 在 ES6 中新增的原始型別 —— Symbol,我也說明了它如何在程式中解決屬性衝突、隱藏內部資料,以及與 TypeScript 型別的結合。
Symbol 的核心特點是「唯一且不可重複」。
每次呼叫 Symbol() 都會回傳一個全新的值,即使描述文字相同也不會相等。這個特性使它非常適合用作物件的屬性鍵,因為:
- 不會與其他屬性名稱衝突;
- 不會被
Object.keys()、for...in或JSON.stringify()列舉出來; - 能在共用或外部擴充的物件上安全地存放資料。