1. 程式人生 > 程式設計 >Javascript Symbol原理及使用方法解析

Javascript Symbol原理及使用方法解析

Symbol是ES6中新引入的一種基本資料型別,在此之前JavaScript中已有幾種基本資料型別:

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

不同於其他基本型別的通俗易懂,Symbol 是什麼和有什麼用一直有些讓人困惑。

什麼是Symbol

JavaScript標準中規定物件的key只能是 String 或 Symbol 型別,區別在於 String 型別的key可以重複而 Symbol 型別的key是唯一的。Symbol 的本質是表示一個唯一標識。每次建立一個Symbol,它所代表的值都不可能重複,該值的內部實現可以視為一段數字(類似:3423498431987719455..)。所以理論上 Symbol 的存在只有一個意義:用於必須使用唯一值的場景。

建立Symbol

建立 Number、String等基本型別的例項有兩種方法:通過建構函式(或者叫工廠函式)和文字語法糖。比如:

// 建構函式
const num = Number(3);
const str = String('hi');

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

顯然使用語法糖更加簡潔。但是 Symbol 只能通過建構函式 Symbol() 進行建立:

const sym = Symbol();

或者,我們可以傳入一個字串引數(descriptor)用於描述該Symbol:

const sym = Symbol('cat');

注意:傳入的引數對 Symbol 值的產生並無影響,因為就算每次傳入的引數都一樣,生成的Symbol值也是不等的。該引數的作用僅用於描述被建立的Symbol,以便debug時可以識別出Symbol的含義。 所以,下列等式結果為 false:

Symbol('cat') === Symbol('cat') // false
Symbol.for(key)

和 Symbol() 類似,Symbol.for(key) 也可以建立一個Symbol,不一樣的是:建立的 Symbol 是全域性的(在全域性Symbol表中註冊),而如果全域性已經存在相同 key 的Symbol,則直接返回該Symbol。所以,下列等式結果為 true:

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

如何使用Symbol

其實 Symbol 本身很簡單,但是如何把它用好、且用的恰到好處卻使人困惑,因為在平常工作中並沒有多少非Symbol不用的場景。但是用對了Symbol會對你的程式碼質量有不少提升。來看下面幾種案例:

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...in,Object.keys()、Object.hasOwnProperty()等方法不能識別Symbol屬性,簡而言之Symbol屬性對使用者是“隱藏”的(但並不是private的,因為有其他途徑可以獲取Symbol屬性),例如:

Javascript Symbol原理及使用方法解析

因此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 屬性也帶出來:

Javascript Symbol原理及使用方法解析

為了隱藏count屬性,更方便的對todo進行操作,我們可以使用Symbol來儲存它,TodoList 類修改為:

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

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

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

Javascript Symbol原理及使用方法解析

當我們想獲取儲存在Symbol中的原資料時,可以使用 Object.getOwnPropertySymbols() 方法:

Javascript Symbol原理及使用方法解析

以上是我能想到的 Symbol 的用途,如果大家有其他心得體會歡迎補充。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。