學長突然問我用過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可以查閱相關文件😏