JavaScript ES6 Symbol 資料型態

ES6 引入了一種新的基本資料型態 (primitive data types),叫做 Symbol (符號),用來表示獨一無二 (unique) 的值。

什麼叫獨一無二?在 ES5 中,object 的屬性 (property) 只能是字串,如果你要幫一個 object 添加新的屬性,很容易會造成名稱衝突,Symbol 就是用來解決這件事情的。也就是說,object property name 現在可以有兩種型態 - 字串和 Symbol。

Symbol 值透過 Symbol() 函數來生成:

let s1 = Symbol();

// "symbol"
typeof s1;

let s2 = Symbol();

// false
s1 === s2;

Symbol() 是一個函數,不是 constructor,你不能 new 它:

// TypeError: Symbol is not a constructor
var s = new Symbol();

Symbol 函數也可以接受一個字串參數,來對該 Symbol 物件命名,主要是為了在 console 顯示,或轉型成字串時,方便區分不同的 Symbol:

let s1 = Symbol('foo');
let s2 = Symbol('bar');

// Symbol(foo)
s1;
// Symbol(bar)
s2;

// "Symbol(foo)"
s1.toString();
// "Symbol(bar)"
s2.toString();

// 建立一個和 s1 同名的 s3
let s3 = Symbol('foo');

// 雖然同名,但 s1 和 s3 還是不一樣的值
// false
s1 === s3;

Symbol 當作屬性名稱 (Properties)

由於每個 Symbol 值都是不相等的,所以用 Symbol 來當作物件的屬性名稱,可以確保不會出現同名的屬性,這特性能防止一個物件的屬性不會在其他地方被意外的覆蓋掉。

var mySymbol = Symbol();

// 下面三種寫法都可以定義一個 Symbol 屬性名稱

// 寫法 1
var a = {};
a[mySymbol] = 'Hello!';

// 寫法 2
var a = {
    [mySymbol]: 'Hello!'
};

// 寫法 3
var a = {};
Object.defineProperty(a, mySymbol, {value: 'Hello!'});

// "Hello!"
a[mySymbol];

用 Symbol 當屬性名時,不能用 . 點運算子,因為用點運算子,會被當作是字串,而不是 Symbol:

var mySymbol = Symbol();
var o = {};

o.mySymbol = 'fooish.com';

// undefined
o[mySymbol];

// "fooish.com"
o['mySymbol'];

同樣道理,在 object literal 中加 Symbol 屬性,你必須用 computed property 語法:

let s = Symbol();

let obj = {
    [s]: function() {}
};

Symbol 也常被用來宣告常數值 (constant),因為用 Symbol 你可以確保所有的值都是不同的:

const FLAG_A = Symbol();
const FLAG_B = Symbol();

function doSomething(flag) {
    switch (flag) {
        case FLAG_A:
            // ...
            break;
        case FLAG_B:
            // ...
            break;
        default:
            throw new Error('Undefined flag');
    }
}

Global Symbols - Symbol.for() / Symbol.keyFor()

有時候,你會希望重複利用同一個 Symbol 值,你可以用 Symbol.for()Symbol.keyFor() 來存取全域的 Symbol 值 (global symbol registry)。

  • Symbol.for(key) 用來取得名稱為 key (字串) 的 global Symbol,如果不存在則會先建立一個新的存到 global symbol registry 後再返回
  • Symbol.keyFor(sym) 用來取得某個 global Symbol 的 key 名稱

Symbol.for(key) 例子:

// 建立一個 global Symbol
Symbol.for('foo');

// 不會再重複建立,會直接返回已經建立的 Symbol
Symbol.for('foo');

// true
Symbol.for('bar') === Symbol.for('bar');

// false
Symbol('bar') === Symbol('bar');

// key 名稱也會被當成 Symbol 名稱
var sym = Symbol.for('mario');
// "Symbol(mario)"
sym.toString();

Symbol.keyFor(sym) 例子:

var globalSym = Symbol.for('foo');

// "foo"
Symbol.keyFor(globalSym);

var localSym = Symbol();
// 如果沒有這個 global Symbol 會返回 undefined

// undefined
Symbol.keyFor(localSym);

Object.getOwnPropertySymbols()

Symbol 的屬性名稱不能被遍歷,像是 for...in, for...of, Object.keys(), Object.getOwnPropertyNames(), JSON.stringify() 都不會返回 Symbol 屬性名。

你要取得所有的 Symbol 屬性名稱可以使用 Object.getOwnPropertySymbols() 方法,該方法會返回一個陣列。

var obj = {};

obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';

for (var i in obj) {
    console.log(i);
}

// 依序輸出
// "c"
// "d"

var objectSymbols = Object.getOwnPropertySymbols(obj);

// [Symbol(a), Symbol(b)]
objectSymbols;