Javascript Symbol 解惑

Symbol是ES6中新引入的一種基本數據類型,在此以前Javascript中已有幾種基本數據類型:函數

  • Numberg
  • String
  • Boolean
  • Null
  • Undefined
  • Object

不一樣於其餘基本類型的通俗易懂,Symbol 是什麼和有什麼用一直有些讓人困惑。this

什麼是Symbol

JavaScript標準中規定對象的key只能是 StringSymbol 類型,區別在於 String 類型的key能夠重複而 Symbol 類型的key是惟一的。Symbol 的本質是表示一個惟一標識。每次建立一個Symbol,它所表明的值都不可能重複,該值的內部實現能夠視爲一段數字(相似:3423498431987719455..)。因此理論上 Symbol 的存在只有一個意義:用於必須使用惟一值的場景。spa

建立Symbol

建立 Number、String等基本類型的實例有兩種方法:經過構造函數(或者叫工廠函數)和文字語法糖。好比:debug

// 構造函數
const num = Number(3);
const str = String('hi');

// 語法糖
const num = 3;
const str = 'hi';

顯然使用語法糖更加簡潔。可是 Symbol 只能經過構造函數 Symbol() 進行建立:code

const sym = Symbol();

或者,咱們能夠傳入一個字符串參數(descriptor)用於描述該Symbol:對象

const sym = Symbol('cat');

注意:傳入的參數對 Symbol 值的產生並沒有影響,由於就算每次傳入的參數都同樣,生成的Symbol值也是不等的。該參數的做用僅用於描述被建立的Symbol,以便debug時能夠識別出Symbol的含義。 因此,下列等式結果爲 falseblog

Symbol('cat') === Symbol('cat') // false

Symbol.for(key)

Symbol() 相似,Symbol.for(key) 也能夠建立一個Symbol,不同的是:建立的 Symbol 是全局的(在全局Symbol表中註冊),而若是全局已經存在相同 key 的Symbol,則直接返回該Symbol。因此,下列等式結果爲 trueip

Symbol.for('cat') === Symbol.for('cat') // true

如何使用Symbol

其實 Symbol 自己很簡單,可是如何把它用好、且用的恰到好處卻令人困惑,由於在日常工做中並無多少非Symbol不用的場景。可是用對了Symbol會對你的代碼質量有很多提高。來看下面幾種案例:rem

1. 用做對象的key,防止命名衝突

使用Symbol做爲Object的key,能夠保證和其餘key都不重複。所以,Symbol很是適合用於對對象的屬性進行拓展。字符串

好比,當使用 String 做爲對象的key時,一旦出現重複的key則後面的屬性會覆蓋前面的:

const persons = {
  'bruce': 'wayne',
  'bruce': 'banner'
}

console.log(persons.bruce); // 'wayne'

使用Symbol做爲Key能夠避免這種狀況:

const bruce1 = Symbol('bruce');
const bruce2 = Symbol('bruce');

const persons = {
  [bruce1]: 'wayne',
  [bruce2]: 'banner'
}

console.log(persons[bruce1]); // 'wayne'
console.log(persons[bruce2]); // 'banner'

JS不少內建的方法都是經過 Symbol 進行指定的,好比:Symobol.iterator 指定了一個iterable對象的迭代器方法;Symbol.replace 指定了對象字符串替換的方法,這類 Symbol 被稱爲 Well-know Symbols,表明了JS語言的內部行爲。

2. 使用Symbol定義枚舉

因爲Javascript並不自帶枚舉類型,一般狀況下咱們會使用一個freezed的Object來模擬枚舉類型,好比定義一個日期的枚舉:

const DAYS = Object.freeze({
  monday: 1,
  tuesday: 2,
  wednesday: 3
});

此時有一個方法,接收 DAYS 的枚舉值來返回當天要作的事:

function getTodo(day) {
  switch (day) {
    case DAYS.monday:
      return "看電影";
    case DAYS.tuesday:
      return "購物";
    case DAYS.wednesday:
      return "健身";
    default:
      return "日期錯誤";
  }
}

咱們但願代碼邏輯足夠嚴謹,傳入的參數嚴格按照 DAYS.monday 的形式,不然就返回日期錯誤,可是該枚舉類型的實現卻作不到。好比:getTodo(1) 依然能獲得 「看電影」 這個結果。

可是使用Symbol卻能夠解決這一問題,DAYS 枚舉類型能夠從新定義爲:

const DAYS = Object.freeze({
  monday: Symbol('monday'),
  tuesday: Symbol('tuesday'),
  wednesday: Symbol('wednesday')
});

此時 getTodo 方法必須接收 DAYS.monday 這樣的枚舉值做爲參數,不然就返回 「日期錯誤」,由於世界上再沒有任何一個值和 DAYS.monday 相等了。

這樣定義枚舉顯然更嚴謹了。

3. 使用Symbol存儲元數據

Key爲Symbol類型的屬性是不能被枚舉的,這是 Symbol 除了惟一性外的第二大特性,所以使用for...inObject.keys()Object.hasOwnProperty()等方法不能識別Symbol屬性,簡而言之Symbol屬性對用戶是「隱藏」的(但並非private的,由於有其餘途徑能夠獲取Symbol屬性),例如:

4

所以Symbol做爲「隱藏」屬性能夠用來存儲對象的元數據。好比,有一個 TodoList

class TodoList {
  constructor() {
    // todo數量
    this.count = 0;
  }

  // 增長todo
  add(id, content) {
    this[id] = content;
    this.count++;
  }
}

const list = new TodoList();

咱們使用 add() 方法向其中增長几個todo:

list.add('a', '看電影');
list.add('b', '購物');
list.add('c', '健身');

當咱們想使用 for...in 查看裏面全部的todo時,會把 count 屬性也帶出來:

1

爲了隱藏count屬性,更方便的對todo進行操做,咱們可使用Symbol來存儲它,TodoList 類修改成:

const count = Symbol('count');
class TodoList {
  constructor() {
    this[count] = 0;
  }

  add(id, content) {
    this[id] = content;
    this[count]++;
  }
}

當咱們再遍歷 TodoList 的時候,count就隱藏了:

2

當咱們想獲取存儲在Symbol中的原數據時,可使用 Object.getOwnPropertySymbols() 方法:

3

以上是我能想到的 Symbol 的用途,若是你們有其餘心得體會歡迎補充。

相關文章
相關標籤/搜索