[JS] Symbols 的使用
let uniqueSymbol = Symbol('<key>');
let sharedSymbol = Symbol.for('<key>');
Symbol.keyFor('<SYMBOL>'); // 取得該 Symbol 的 Key
觀念
- ES6 引入了一種新的原始數據(primitive type)類型
Symbol
,表示獨一無二的值。它是 JavaScript 語言的第七種數據類型,前六種是:Undefined
、Null
、Boolean
、String
、Number
、Object
。 - 透過
symbol
建立的值都是獨特的(unique),因此可以作為獨特不重複的物件屬性名稱。 - 以 Symbol 為鍵的物件屬性與陣列元素類似,不能被類似
obj.name
的點運算符存取,你必須使用方括號[]
訪問這些屬性。 - Symbol 作為屬性名,該屬性不會出現在
for...in
、for...of
迴圈中,也不會被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols
方法,可以獲取指定對象的所有 Symbol 屬性名。 - 另一個新的 API,
Reflect.ownKeys
方法可以返回所有類型的鍵名,包括常規鍵名和 Symbol 鍵名。 - 由於以 Symbol 值作為名稱的屬性,不會被常規方法 遍歷得到。我們可以利用這個特性,為對象定義一些非私有的、但又希望只用於內部的方法。
建立 Symbol
建立 Symbol 不需要 new
。:
let symbol = Symbol('<key>');
console.log(typeof symbol); // symbol
Symbol(), Symbol.for(),Symbol.keyFor()
- 我們希望重新使用同一個 Symbol 值,
Symbol.for
方法可以做到這一點 Symbol()
:內部會建立一個獨特的 id(unique id) ,兩個相同 key 的 Symbol 是不同的,且無法透過Symbol.keyFor()
找到。Symbol.for()
:一樣都會生成新的 Symbol,但兩個相同 key 的 Symbol 會是相同的(reused id),且可以透過Symbol.keyFor()
找到。Symbol.keyFor()
方法返回一個已登記的 Symbol 類型值的 key。
/**
* Symbol() 每次都是新的,兩次生成的並不"相同",無法在全域被搜尋。
* Symbol.for() 兩次生成的會是相同的,可以在全域被 Symbol.keyFor() 搜尋到。
**/
let a = Symbol('Symbol A');
let aa = Symbol('Symbol A');
let b = Symbol.for('Symbol B');
let bb = Symbol.for('Symbol B');
a === aa; // false
b === bb; // true
透過 Symbol.keyFor()
取得某登記過的 symbol 鍵名
/**
* 使用 Symbol.keyFor() 查看某一變數的 Symbol
**/
Symbol.keyFor(a); // undefined
Symbol.keyFor(aa); // undefined
Symbol.keyFor(b); // Symbol B
Symbol.keyFor(bb); // Symbol B
See the Pen ES6 Symbol Demo Code by PJCHEN (@PJCHENder) on CodePen.
給予物件 Symbol
var mySymbol = Symbol();
// 第一種寫法
var a = {};
a[mySymbol] = 'Hello!';
// 第二種寫法
var a = {
[mySymbol]: 'Hello!',
};
// 第三種寫法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上寫法都得到同樣結果
a[mySymbol]; // "Hello!"
取得物件中的 Symbol
- Symbol 是無法透過疊代的方式(例如
for...in
)或getOwnPropertyNames
,Object.keys()
取得 - 可以使用
Object.getOwnPropertySymbols(obj)
或Reflect.ownKeys()
取得物件的 Symbol:
var obj = {
name: 'Aaron',
};
var a = Symbol('aaa');
var b = Symbol('bbb');
obj[a] = 'Hello';
obj[b] = 'World';
/**
* obj
* {
* name: "Aaron",
* Symbol(aaa): "Hello",
* Symbol(bbb): "World"
* }
**/
/**
* 可以取得 Symbols 的方式
**/
// 使用 Object.getOwnPropertySymbols() 取得物件的 Symbol
Object.getOwnPropertySymbols(obj); // [Symbol(a), Symbol(b)]
// 使用 Reflect.ownKeys() 取得物件所有的 key
Reflect.ownKeys(obj); // ["name", Symbol(aaa), Symbol(bbb)]
/**
* 無法取得 Symbols 的方式
**/
// 無法使用 Object.keys() 取得 Symbol
Object.keys(obj);
// 無法使用 for...in 取得 Symbol
for (let prop in obj) {
console.log(`${prop}: ${obj[prop]}`); // name: Aaron
}
// 無法使用 Object.getOwnPropertyNames() 取得 Symbol
Object.getOwnPropertyNames(obj); // ['name']
See the Pen ES6 Symbol Demo Code - 2 by PJCHEN (@PJCHENder) on CodePen.
使用範例
shared Symbol
/**
* Shared Symbols
**/
let symbolGlobal = Symbol.for('shared');
/**
* 透過 dynamic property 的方法把 symbol 設成 key
**/
let person = {
firstName: 'Aaron',
lastName: 'Chen',
};
function registerMember(memberObj) {
// 在 function 中設定的 symbol 一樣可以影響到外層的
let symbolScope = Symbol.for('shared');
memberObj[symbolScope] = 'secret';
person[symbolGlobal]; // secret
person[symbolScope]; // secret
}
registerMember(person);
person[symbolGlobal]; // secret
person[symbolScope]; // symbolScope is not defined
Demo Code @ JSFiddle
Symbol in JavaScript
在 JavaScript 有許多內建好的 Symbol,這些 Symbol 通常包含 JS 中內建好的方法,我們可以到 MDN 中看這些方法 Well Known Symbols。
如果我們修改內建 Symbol 所提供的方法,將可以變更原生的方法:
/**
* Change Original Function of JavaScript
**/
let number = [1, 2, 3];
number[Symbol.toPrimitive] = function () {
return 999;
};
console.log(number + 1); // 原本會回傳 "1,2,31";在修改後會回傳 1000