跳至主要内容

[JS] Symbols 的使用

let uniqueSymbol = Symbol('<key>');
let sharedSymbol = Symbol.for('<key>');
Symbol.keyFor('<SYMBOL>'); // 取得該 Symbol 的 Key

觀念

  • ES6 引入了一種新的原始數據(primitive type)類型 Symbol表示獨一無二的值。它是 JavaScript 語言的第七種數據類型,前六種是:UndefinedNullBooleanStringNumberObject
  • 透過 symbol 建立的值都是獨特的(unique),因此可以作為獨特不重複的物件屬性名稱。
  • 以 Symbol 為鍵的物件屬性與陣列元素類似,不能被類似 obj.name 的點運算符存取,你必須使用方括號 [] 訪問這些屬性
  • Symbol 作為屬性名,該屬性不會出現在 for...infor...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

參考資料