PJCHENder 未整理筆記

[JS] Symbols 的使用

2017-09-26

[JS] Symbols 的使用

@(Javascript)[ES6, JavaScript]

1
2
3
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。:

1
2
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 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 鍵名

1
2
3
4
5
6
7
8
/**
* 使用 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 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 所提供的方法,將可以變更原生的方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* Change Original Function of JavaScript
**/

let number = [1,2,3]

number[Symbol.toPrimitive] = function () {
return 999
}

console.log(number + 1) // 原本會回傳 "1,2,31";在修改後會回傳 1000

參考資料

掃描二維條碼,分享此文章