@ddprrt翻譯:瘋狂的技術宅javascript
原文:https://fettblog.eu/symbols-i...前端
未經容許嚴禁轉載java
symbol
是 JavaScript 和 TypeScript 中的原始數據類型,可用於對象屬性。與 number
和 string
相比,symbol
具備一些獨特的功能,使它脫穎而出。程序員
能夠用 Symbol()
工廠函數建立符號:面試
const TITLE = Symbol('title')
Symbol
沒有構造函數。該參數是可選描述。經過調用工廠函數,爲 TITLE
分配了這個新建立的符號的惟一值。此符號如今是惟一的,可與全部其餘符號區分開,而且不會與具備相同描述的任何其餘符號衝突。typescript
const ACADEMIC_TITLE = Symbol('title') const ARTICLE_TITLE = Symbol('title') if(ACADEMIC_TITLE === ARTICLE_TITLE) { // THis is never true }
該描述可幫助你在開發期間獲取有關符號的信息:segmentfault
console.log(ACADEMIC_TITLE.description) // title console.log(ACADEMIC_TITLE.toString()) // Symbol(title)
若是你想擁有獨特且惟一的可比值,那麼符號就很棒。對於運行時切換或模式比較:安全
// A shitty logging framework const LEVEL_INFO = Symbol('INFO') const LEVEL_DEBUG = Symbol('DEBUG') const LEVEL_WARN = Symbol('WARN') const LEVEL_ERROR = Symbol('ERROR') function log(msg, level) { switch(level) { case LEVEL_WARN: console.warn(msg); break case LEVEL_ERROR: console.error(msg); break; case LEVEL_DEBUG: console.log(msg); debugger; break; case LEVEL_INFO: console.log(msg); } }
符號也可用做屬性鍵,但不可迭代,這對序列化頗有用服務器
const print = Symbol('print') const user = { name: 'Stefan', age: 37, [print]: function() { console.log(`${this.name} is ${this.age} years old`) } } JSON.stringify(user) // { name: 'Stefan', age: 37 } user[print]() // Stefan is 37 years old
存在一個全局符號註冊表,容許你在整個程序中訪問 token。微信
Symbol.for('print') // creates a global symbol const user = { name: 'Stefan', age: 37, // uses the global symbol [Symbol.for('print')]: function() { console.log(`${this.name} is ${this.age} years old`) } }
首先調用 Symbol.for
建立一個符號,第二個調用使用相同的符號。若是將符號值存儲在變量中並想知道鍵,則可使用 Symbol.keyFor()
const usedSymbolKeys = [] function extendObject(obj, symbol, value) { //Oh, what symbol is this? const key = Symbol.keyFor(symbol) //Alright, let's better store this if(!usedSymbolKeys.includes(key)) { usedSymbolKeys.push(key) } obj[symnbol] = value } // now it's time to retreive them all function printAllValues(obj) { usedSymbolKeys.forEach(key => { console.log(obj[Symbol.for(key)]) }) }
漂亮!
TypeScript 徹底支持符號,它是類型系統中的主要成員。 symbol
自己是全部可能符號的數據類型註釋。請參閱前面的 extendObject
函數。爲了容許全部符號擴展咱們的對象,可使用 symbol
類型:
const sym = Symbol('foo') function extendObject(obj: any, sym: symbol, value: any) { obj[sym] = value } extendObject({}, sym, 42) // Works with all symbols
還有子類型 unique symbol
。 unique symbol
與聲明緊密相關,只容許在 const 聲明中引用這個確切的符號。
你能夠將 TypeScript 中的名義類型視爲 JavaScript 中的名義值。
要得到 unique symbol
的類型,你須要使用 typeof 運算符。
const PROD: unique symbol = Symbol('Production mode') const DEV: unique symbol = Symbol('Development mode') function showWarning(msg: string, mode: typeof DEV | typeof PROD) { // ... }
符號位於 TypeScript 和 JavaScript 中名義類型和不透明類型的交集。而且是咱們在運行時最接近標稱類型檢查的事情。這是一種用來重建像 enum
這樣結構的很好的方法。
一個有趣的符號例子是在 JavaScript 中從新建立運行時的 enum
行爲。 TypeScript 中的 enum 是不透明的。這實際上意味着你不能將字符串值分配給 enum
類型,由於 TypeScript 會將它們視爲惟一的:
enum Colors { Red = 'Red', Green = 'Green', Blue = 'Blue', } const c1: Colors = Colors.Red; const c2: Colors = 'Red'; // 💣 沒法直接分配
若是你作一下比較,會發現很是有趣:
enum Moods { Happy = 'Happy', Blue = 'Blue' } // 💣 This condition will always return 'false' since the // types 'Moods.Blue' and 'Colors.Blue' have no overlap. if(Moods.Blue === Colors.Blue) { // Nope }
即便使用相同的值類型,在枚舉中它們也足夠獨特,以便 TypeScript 認爲它們不具備可比性。
在 JavaScript 領域,咱們可使用符號建立相似的枚舉。在如下例子中查看彩虹和黑色的顏色。咱們的「枚舉」 Colors
僅包含顏色而並不是黑色的符號:
// All Color symbols const COLOR_RED: unique symbol = Symbol('RED') const COLOR_ORANGE: unique symbol = Symbol('ORANGE') const COLOR_YELLOW: unique symbol = Symbol('YELLOW') const COLOR_GREEN: unique symbol = Symbol('GREEN') const COLOR_BLUE: unique symbol = Symbol('BLUE') const COLOR_INDIGO: unique symbol = Symbol('INDIGO') const COLOR_VIOLET: unique symbol = Symbol('VIOLET') const COLOR_BLACK: unique symbol = Symbol('BLACK') // All colors except Black const Colors = { COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_GREEN, COLOR_BLUE, COLOR_INDIGO, COLOR_VIOLET } as const;
咱們能夠像使用 enum
同樣使用這個 symbol:
function getHexValue(color) { switch(color) { case Colors.COLOR_RED: return '#ff0000' //... } }
而且 symbol 沒法比較:
const MOOD_HAPPY: unique symbol = Symbol('HAPPY') const MOOD_BLUE: unique symbol = Symbol('BLUE') // 除黑色外的全部顏色 const Moods = { MOOD_HAPPY, MOOD_BLUE } as const; // 💣 由於類型,這種狀況老是會返回'false' // 'typeof MOOD_BLUE' 和 'typeof COLOR_BLUE' 並不重疊. if(Moods.MOOD_BLUE === Colors.COLOR_BLUE) { // Nope }
咱們要添加一些 TypeScript 註釋:
unique symbols
,這意味着咱們分配符號的常量永遠不會改變。as const
。有了它,TypeScript 就會將類型設置爲容許每一個符號,只容許咱們定義的徹底相同的符號。這容許咱們在爲函數聲明定義符號「枚舉」時得到更多的類型安全性。咱們從輔助類型開始,從對象中獲取全部值類型。
type ValuesWithKeys<T, K extends keyof T> = T[K]; type Values<T> = ValuesWithKeys<T, keyof T>
記住,咱們使用了 as const
,這意味着咱們的值被縮小到精確的值類型(例如,類型是 COLOR_RED
)而不是它們的整體類型(symbol
)。
有了它,就能夠聲明咱們的功能:
function getHexValue(color: Values<typeof Colors>) { switch(color) { case COLOR_RED: // super fine, is in our type case Colors.COLOR_BLUE: // also super fine, is in our type break; case COLOR_BLACK: // what? What is this??? TypeScript errors 💥 break; } }
若是使用 symbol 鍵和值而不是僅使用 symbol 值,則能夠刪除輔助和 const 上下文:
const ColorEnum = { [COLOR_RED]: COLOR_RED, [COLOR_YELLOW]: COLOR_YELLOW, [COLOR_ORANGE]: COLOR_ORANGE, [COLOR_GREEN]: COLOR_GREEN, [COLOR_BLUE]: COLOR_BLUE, [COLOR_INDIGO]: COLOR_INDIGO, [COLOR_VIOLET]: COLOR_VIOLET, } function getHexValueWithSymbolKeys(color: keyof typeof ColorEnum) { switch(color) { case ColorEnum[COLOR_BLUE]: // 👍 break; case COLOR_RED: // 👍 break; case COLOR_BLACK: // 💥 break; } }
這樣,經過 TypeScript 的獨特符號,你能夠在編譯時得到類型安全性、運行時的實際類型安全性以及 JavaScript 的 `unique Symbol
的特性。