內容概要
前言
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