內容概要

前言

Symbol基礎 (老司機可跳過)

使用場景討論

總結

前言

Symbol是ES6引入的一個新特性 —— 新到什麼程度呢?ES5之前是沒有任何辦法可以模擬Symbol的

但是,我們日常開發工作中,直接使用到Symbol的場景似乎很少。我在網上搜了很多資料,對Symbol開始逐漸加深了理解,接下來就談談我的一些看法。

Symbol 基礎知識

symbol 是一種全新的

基本資料型別

(primitive data type)

Symbol()

函式會返回symbol型別的值,作為建構函式來說它並不完整,因為它不支援語法:

new Symbol() // Uncaught TypeError: Symbol is not a constructor

每個從

Symbol()

返回的symbol值都是

唯一

的,使用Symbol()建立新的symbol值,並用一個可選的字串作為其描述 —— 描述相同的兩個Symbol值依然是不同的

const symbol1 = Symbol();

const symbol2 = Symbol(42);

const symbol3 = Symbol(‘foo’); //描述

console。log(typeof symbol1); // “symbol”

console。log(symbol2 === 42); // false

console。log(symbol3。toString()); // “Symbol(foo)”

console。log(Symbol(‘foo’) === Symbol(‘foo’)); // false

一個

symbol值能作為物件屬性的識別符號

—— 這是該資料型別

僅有的目的

(劃重點)

const obj = {};

const myPrivateMethod = Symbol();

obj[myPrivateMethod] = function() {。。。};

當一個 symbol 型別的值在屬性賦值語句中被用作識別符號,該屬性(像這個 symbol 一樣)是

匿名

的, 並且是

不可列舉

的 —— 因為這個屬性是不可列舉的,它不會在迴圈結構

for( ... in ...)

中作為成員出現,也因為這個屬性是匿名的,它同樣不會出現在

Object.getOwnPropertyNames()

的返回數組裡。

這個屬性可以透過建立時的原始 symbol 值訪問到,或者透過遍歷

Object.getOwnPropertySymbols()

返回的陣列。

在上面的程式碼示例中,只有透過儲存在變數 myPrivateMethod的值可以訪問到物件屬性(劃重點)

const obj = {};

const aProperty = Symbol(“a”);

obj[aProperty] = “a”;

obj[Symbol。for(“b”)] = “b”;

obj[“c”] = “c”;

obj。d = “d”;

for (let i in obj) {

console。log(i); // 輸出 “c” 和 “d”

}

console。log(Object。getOwnPropertySymbols(obj)); // [ Symbol(a), Symbol(b) ]

console。log(obj[aProperty]); // a

console。log(obj[Symbol。for(“b”)]); // b

全域性共享的Symbol

JavaScript 有個全域性 symbol 登錄檔

Symbol。for() 引數為字串

使用給定的key搜尋現有的symbol,如果找到則返回該symbol。否則將使用給定的key在全域性symbol登錄檔中建立一個新的symbol

Symbol。keyFor() 引數為Symbol

從全域性symbol登錄檔中,為給定的symbol檢索一個共享的key(字串)

const a = Symbol。for(‘foo’);

const b = Symbol。for(‘foo’);

console。log(a === b); //true

const aKey = Symbol。keyFor(a);

console。log(aKey); //foo

const isEqual = Symbol。keyFor(Symbol。for(“tokenString”)) === “tokenString”;

console。log(isEqual); //true

Symbol 類具有一些靜態屬性,這類屬性只有幾個, 即所謂的眾所周知的 symbol。

它們是在某些內建物件中找到的某些特定方法屬性的 symbol。 暴露出這些 symbol 使得可以直接訪問這些行為;這樣的訪問可能是有用的,例如在定義自定義類的時候。 普遍的 symbol 的例子有:“Symbol。hasInstance”用於 instanceof ,“Symbol。iterator”用於類似陣列的物件,“Symbol。search”用於字串物件

以Symbol。hasInstance為例:

class MyClass {

static [Symbol。hasInstance](lho) {

return Array。isArray(lho);

}

}

console。log([1,2,3] instanceof MyClass); // true

所有眾所周知的 symbol 列表(本文不詳細介紹,有興趣的自行查閱文件):

Symbol。iterator

Symbol。asyncIterator

Symbol。match

Symbol。replace

Symbol。search

Symbol。split

Symbol。hasInstance

Symbol。isConcatSpreadable

Symbol。unscopables

Symbol。species

Symbol。toPrimitive

Symbol。toStringTag

(對這些眾所周知的Symbol有點困惑?不要緊,本文後面會進一步闡述這些的使用場景)

Symbol 的使用場景

從上面的 Symbol 基礎知識,我們知道有且僅有 2 種途徑可以建立 Symbol 值 —— Symbol() 函式 和 Symbol。for() 方法

使用 Symbol() 函式創建出來的 Symbol 是獨一無二的(引數字串對此毫不影響)

for (let i = 0; i < 100; i++) {

Symbol(‘test’)

}

即 上面程式碼建立的100個Symbol值都互不相同

這種唯一性在某些定義常量的場景帶來了極大的便利

使用場景一:定義常量

假設你正在開發一個日誌記錄模組,你希望提供 DEBUG,INFO,WARN 三種級別的日誌

log。levels = {

DEBUG: Symbol(‘debug’),

INFO: Symbol(‘info’),

WARN: Symbol(‘warn’),

};

log(log。levels。DEBUG, ‘debug message’);

log(log。levels。INFO, ‘info message’);

在上面程式碼中,其實我們並不關心 DEBUG,INFO,WARN 的值的意義,我們僅僅是想獲得三個唯一的值作為

標誌

而已

假如我們使用數字來代替上面的Symbol

log。levels = {

DEBUG: 1,

INFO: 2,

WARN: 3,

};

在新增一個 ERROR級別的時候,我們還要小心不能跟之前的值(1,2,3中的任意一個)相同;或者當我們手滑不小心,把 INFO 的值寫成 1 時,DEBUG 和 INFO 的日誌就混淆在一起了 —— 這些麻煩的根源在於 number 的值並不是獨一無二的

(p。s。 Symbol 在這邊的作用有點類似於Java中 的 Enum )

使用場景二:在物件中存放自定義元資料

物件中的元資料可以理解為 —— 相對次要的,不影響物件內容的特殊屬性。

物件的主要內容和屬性是那些可以由 Object。getOwnProperties() 獲取到的屬性。

自定義元資料可以

給目標物件新增標記,以便做特殊處理

作為物件內部的一個“私有”屬性,例:

const size = Symbol(‘size’);

class Collection {

constructor() {

this[size] = 0;

}

add(item) {

this[this[size]] = item;

this[size]++;

}

static sizeOf(instance) {

return instance[size];

}

}

const x = new Collection();

console。log(Collection。sizeOf(x) === 0); // true

x。add(‘foo’);

console。log(Collection。sizeOf(x) === 1); // true

console。log(Object。keys(x)); //[‘0’]

console。log(Object。getOwnPropertyNames(x)); // [‘0’]

console。log(Object。getOwnPropertySymbols(x)); // [ Symbol(size) ]

( 當然,上面這個例子完全可以使用 function 和 閉包實現真正的私有變數,並且我個人也推薦這麼做;這邊的程式碼僅做Symbol的一個使用例子)

提醒:由於Symbol對應的屬性是不可遍歷的,因此 JSON。stringfy 也不會碰這些元資料。

使用場景三:在工具庫開發中埋下hook(鉤子函式)接入點

仍然以日誌庫為例,我們希望使用者呼叫我們的 log 方法時可以自定義某些特殊物件的列印格式。

import console from ‘my-console-lib’;

const inspect = console。Symbols。INSPECT; // 由我們的庫匯出的一個Symbol值

const myVeryOwnObject = {};

console。log(myVeryOwnObject); // 輸出 `{}`

myVeryOwnObject[inspect] = function () { return ‘DUUUDE’; }; // 使用者可以給目標物件設定hook函式

console。log(myVeryOwnObject); // 輸出 `DUUUDE` —— 優先執行使用者的hook

我們的 log 方法可以這麼實現

console。log = function (。。。items) {

let output = ‘’;

for(const item of items) {

if (typeof item[console。Symbols。INSPECT] === ‘function’) {

output += item[console。Symbols。INSPECT](item);

} else {

output += console。inspect[typeof item](item);

}

output += ‘ ’;

}

process。stdout。write(output + ‘\n’);

}

舉個真實的例子:

在redux的原始碼中(github連結)

/**

* Interoperability point for observable/reactive libraries。

* @returns {observable} A minimal observable of state changes。

* For more information, see the observable proposal:

* https://github。com/tc39/proposal-observable

*/

[Symbol。observable](): Observable

使用了 Symbol。observable 這麼一個 Symbol 作為 observable/reactive 庫的接入口。

而 Symbol。observable 來自於一個規範

https://

github。com/tc39/proposa

l-observable

,有興趣的讀者可以進一步研讀。

使用場景四:類似lodash的工具庫

上面 Symbol基礎章節中提到了“眾所周知的Symbol”,它們通常可以用來定製物件的一些行為。

在日常開發中,其實我們很少能接觸到這類需求

。但是,在類似lodash這種工具庫中,為了能做到廣泛的適用性,就需要檢查目標物件是否定義了這些特殊的Symbol,同時也要依據規範在返回的物件中設定好這些Symbol對應的屬性。

舉個例子: lodash 的 toArray。js

/** Built-in value references。 */

const symIterator = Symbol。iterator // 眾所周知的Symbol

function toArray(value) {

if (!value) {

return []

}

if (isArrayLike(value)) {

return isString(value) ? stringToArray(value) : copyArray(value)

}

if (symIterator && value[symIterator]) { // 如果value包含這個Symbol,就利用這個Symbol遍歷value的值

return iteratorToArray(value[symIterator]())

}

const tag = getTag(value)

const func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values)

return func(value)

}

export default toArray

除非你的工作專案就是這類庫,否則對這些特殊的Symbol只需要簡單瞭解就足夠了(個人看法)。

總結

Symbol 是 ES6 引入的一個全新的基礎資料型別,它只擁有一些少量且簡單的特性。

在合適的場景下,Symbol能發揮出更高效且靈活的作用。

歡迎分享你對Symbol的理解和使用場景,如果覺得這篇文章對你有幫助,記得一鍵三連!

參考文章和連結:

https://

developer。mozilla。org/z

h-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol

2。 Symbol

3。 Metaprogramming in ES6: Symbols and why they‘re awesome

4。

https://

github。com/lodash/lodas

h/blob/86a852fe763935bb64c12589df5391fd7d3bb14d/toArray。js#L16