學長突然問我用過Symbol嗎,我哽咽住了(準備捱罵)

語言: CN / TW / HK

theme: cyanosis

我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第1篇文章,點擊查看活動詳情

這天在實驗室和學長一起寫學校的項目,學長突然問我一句:“你用過Symbol嗎?” 然而我的大腦卻遍歷不出這個關鍵性名詞,啊,又要補漏了

Symbol對於一些前端小白(比如我)來講,沒有特別使用過,只是在學習JS的時候瞭解了大概的概念,當時學習可能並沒有感覺到Symbol在開發中有什麼特別的作用,而在學習一段時間後回頭看一遍,頓悟!

而本文將帶讀者從基本使用特性應用內置Symbol三個方面,帶大家深入Symbol這個神奇的類型!

什麼是Symbol😶‍🌫️

Symbol作為原始數據類型的一種,表示獨一無二的值,在之前,對象的鍵以字符串的形式存在,所以極易引發鍵名衝突問題,而Symbol的出現正是解決了這個痛點,它的使用方式也很簡單。

Symbol的使用

創建一個Symbol與創建Object不同,只需要a = Symbol()即可

let a = Symbol() typeof a

使用時需要注意的是:不可以使用new來搭配Symbol()構造實例,因為其會拋出錯誤

let a = new Symbol() typeof a // Symbol is not a constructor

通常使用new來構造是想要得到一個包裝對象,而Symbol不允許這麼做,那麼如果我們想要得到一個Symbol()的對象形式,可以使用Object()函數

let a = Symbol() let b = Object(a) typeof b // object

介紹到這裏,問題來了,Symbol看起來都一樣,我們怎麼區分呢?我們需要傳入一個字符串的參數用來描述Symbol()

let a = Symbol() let b = Symbol()

上面看來ab的值都是Symbol,代碼閲讀上,兩者沒區分,那麼我們調用Symbol()函數的時候傳入字符串用來描述我們構建的Symbol()

let a = Symbol("a") let b = Symbol("b")

Symbol的應用✌️

Symbol的應用其實利用了唯一性的特性。

作為對象的屬性

大家有沒有想過,如果我們在不瞭解一個對象的時候,想為其添加一個方法或者屬性,又怕鍵名重複引起覆蓋的問題,而這個時候我們就需要一個唯一性的鍵來解決這個問題,於是Symbol出場了,它可以作為對象的屬性的鍵,並鍵名避免衝突。

let a = Symbol() let obj = {} obj[a] = "hello world"

我在上面創建了一個symbol作為鍵的對象,其步驟如下

  1. 創建一個Symbol
  2. 創建一個對象
  3. 通過obj[]Symbol作為對象的鍵

值得注意的是我們無法使用.來調用對象的Symbol屬性,所以必須使用[]來訪問Symbol屬性

降低代碼耦合

我們經常會遇到這種代碼

if (name === "豬痞惡霸") {    console.log(1) }

又或者

switch (name) {        case "豬痞惡霸"        console.log(1)        case "Ned"        console.log(2) }

在這兩段段代碼中作為判斷控制語句的"豬痞惡霸""Ned"被稱為魔術字符串,即與代碼強耦合的字符串,可以理解為:與我們的程序代碼強制綁定在一起,然而這會導致一個問題,在條件判斷複雜的情況下,我們想要更改我們的判斷條件,就需要更改每一個判斷控制,維護起來非常麻煩,所以我們可以換一種形式來解決字符串與代碼強耦合。

const judge = {    name_1:"豬痞惡霸"    name_2:"Ned" } switch (name) {        case judge.name_1        console.log(1)        case judge.name_2        console.log(2) }

我們聲明瞭一個存儲判斷條件字符串的對象,通過修改對象來自如地控制判斷條件,當然本小節的主題是Symbol,所以還能繼續優化!

const judge = {    rectangle:Symbol("rectangle"),    triangle:Symbol("triangle") } function getArea(model, size) {    switch (model) {        case judge.rectangle:       return size.width * size.height        case judge.triangle:       return size.width * size.height / 2 } } let area = getArea(judge.rectangle ,{width:100, height:200}) console.log(area)

為了更加直觀地瞭解我們優化的過程,上面我創建了一個求面積的工具函數,利用Symbol的特性,我們使我們的條件判斷更加精確,而如果是字符串形式,沒有唯一的特點,可能會出現判斷錯誤的情況。

全局共享Symbol

如果我們想在不同的地方調用已經同一Symbol即全局共享的Symbol,可以通過Symbol.for()方法,參數為創建時傳入的描述字符串,該方法可以遍歷全局註冊表中的的Symbol,當搜索到相同描述,那麼會調用這個Symbol,如果沒有搜索到,就會創建一個新的Symbol

為了更好地理解,請看下面例子

let a = Symbol.for("a") let b = Symbol.for("a") a === b // true

如上創建Symbol

  1. 首先通過Symbol.for()在全局註冊表中尋找描述為aSymbol,而目前沒有符合條件的Symbol,所以創建了一個描述為aSymbol
  2. 當聲明b並使用Symbol.for()在全局註冊表中尋找描述為aSymbol,找到並賦值
  3. 比較ab結果為true反映了Symbol.for()的作用

再來看看下面這段代碼

let a = Symbol("a") let b = Symbol.for("a") a === b // false

woc,結果竟然是false,與上面的區別僅僅在於第一個Symbol的創建方式,帶着驚訝的表情,來一步一步分析一下為什麼會出現這樣的結果

  1. 使用Symbol("a")直接創建,所以該Symbol("a")不在全局註冊表中
  2. 使用Symbol.for("a")在全局註冊表中尋找描述為aSymbol,並沒有找到,所以在全局註冊表中又創建了一個描述為a的新的Symbol
  3. 秉承Symbol創建的唯一特性,所以ab創建的Symbol不同,結果為false

問題又又又來了!我們如何去判斷我們的Symbol是否在全局註冊表中呢?

Symbol.keyFor()幫我們解決了這個問題,他可以通過變量名查詢該變量名對應的Symbol是否在全局註冊表中

let a = Symbol("a") let b = Symbol.for("a") Symbol.keyFor(a) // undefined Symbol.keyFor(b) // 'a'

如果查詢存在即返回該Symbol的描述,如果不存在則返回undefined

以上通過使用Symbol.for()實現了Symbol全局共享,下面我們來看看Symbol的另一種應用

內置Symbol值又是什麼❔

上面的Symbol使用是我們自定義的,而JS有內置了Symbol值,個人的理解為:由於唯一性特點,在對象內,作為一個唯一性的鍵並對應着一個方法,在對象調用某方法的時候會調用這個Symbol值對應的方法,並且我們還可以通過更改內置Symbol值對應的方法來達到更改外部方法作用的效果。

為了更好地理解上面這一大段話,咱們以Symbol.hasInstance作為例子來看看內置Symbol到底是個啥!

class demo {    static [Symbol.hasInstance](item) {        return item === "豬痞惡霸"   } } "豬痞惡霸" instanceof demo // true

Symbol.hasInstance對應的外部方法是instanceof,這個大家熟悉吧,經常用於判斷類型。而在上面的代碼片段中,我創建了一個demo類,並重寫了Symbol.hasInstance,所以其對應的instanceof行為也會發生改變,其內部的機制是這樣的:當我們調用instanceof方法的時候,內部對應調用Symbol.hasInstance對應的方法即return item === "豬痞惡霸"

注:更多相關的內置Symbol可以查閲相關文檔😏