JS 原力覺醒 Day29 - Set / Map

ES6 之後加入兩種新的資料結構:Map 跟 Set 。 Map 與 Set 都是像字串跟陣列這樣可以被尋訪的類型,也就是說可以使用 for 迴圈去一個一個查找跟操作他們的值。今天就來說明一下這兩個類別跟使用方式吧!
https://ithelp.ithome.com.tw/upload/images/20190916/20106580lJIWdcHc2t.png

Outline

  • Set
  • Map
  • Map 與 Object

Set

Set 的中文翻譯與數學裡面的「集合」相同,「集合」是某個定義好並且具有相同性質的元素的集合,講白話一點就是「一堆東西」。在 JS 內的集合當然代表「一堆值」,他跟陣列有點像,差別在 Set 能夠讓開發者可以方便快速的儲存不重複、獨特的數值。至於 Set 內儲存的元素內容沒有型別限制,可以是純值也可以是物件型別。

Set 除了具有儲存不重複數值的性質外,在上面還有一些很方便的方法可以直接處理數值,讓我們陸續來看看,首先創造一個新的 Set ,創造新的 Set 很簡單,只要在 Set 的建構子傳入一個陣列即可:

 let set = new Set([1,2,3,'Hello','World',true]) 

在 Set 類別上有許多方法讓我們可以用比較語意化的方式操作 Set 內容:

  • add( value ) : 新增一個元素到 Set
  • clear() :刪除所有 Set 內的元素
  • delete( value) :刪除 Set 內特定的某個元素
  • forEach() : 跟 array 上的 forEach 功能相同
  • has( value ) :檢查 Set 內有沒有對應值的元素,這個功能如果在陣列內,必須透過 indexOf 來檢查才能達成。
  • values() :會回傳 Set 內所有數值
  • size :回傳 Set 元素長度

就像前面說過的, Set 內儲存的是不重複的元素,因此如果有相同數值的元素再次被傳入,這個數值就會直接被忽略。

	set.size //6
  set.add('Hello') 
  set.size //6

對 Set 做巡訪的方式跟陣列很相似,一樣可以用 forEach 方法,甚至 Set 可以很方便的直接轉為陣列 :

 let setArr = [...set]

這個特性非常好用,利用這點我們就可以很快速的過濾出陣列內的重複值!

 let duplicatedValueArr = [1,2,3,5,10,19,10,4,5,6,3,1,2]
 let uniqueArr  = [...new Set(duplicatedValueArr)]

這樣子是不是既方便快速又簡潔? 如果單純使用陣列可能還需要透過 filter 跟外部變數來儲存重複值輔助檢查,使用 Set 的話,這些功夫都可以省去。

Map

Map 也是跟陣列、跟 Set 具有相同特性且可被巡訪的物件型別,差別在於, Map 跟物件ㄧ樣是鍵值的組合,也就是說,Map 同時具有跟陣列ㄧ樣可以被巡訪的特色,同時也有物件儲存任意屬性跟數值的能力。

Map 類型上的方法也與 Set 大同小異,差別在 Set 新增元素的方法是使用 add ,而 Map 內必須用 set 方法 ,且新增元素時必須傳入兩個參數,第一個是要儲存的鍵 ( key ),另外一個是要儲存的數值內容 ( value )。

創造新的 Map 的方式與創造 Set 相同,但由於 Map 是鍵-值對的結構,傳入建構子內的陣列內不能夠像 Set 那樣只是個單一元素,而必須要是個鍵-值的組合,所以我們可以用二維陣列來達成,大概像是這樣:

 let map = new Map([['name','Luke'],['Hello','World']]) 

取得 Map 元素 :

  map.get('name') // Luke 

新增元素 :

 map.set('Greeting','I am Anakin') // { ... 'Hello'=>'World', 'Greeting'=> 'I am Anakin'}

其他像是刪除特定元素或是刪除所有 Map 內元素則都跟 Set 上的方法差不多:

 map.delete('Hello')  
 map.clear()
 map.size

Map 與 Object

Map 其實跟物件ㄧ樣都是 鍵-值 的組合,事實上這些結構相似的類型有許多種,如,那麼使用 Map 相比於使用物件有什麼好處呢?還記得前面提到在 JS 內除了原始型別以外的型別都是物件型別嗎?這代表除了物件以外像是 Array 以及Function 這樣的型別都是繼承自 Object,這其中當然包含 Map

所以這兩種型別才有這麼相似的結構 ,性質相同的部分就不用多說了,但是這兩者還是有一些不差異,這些差異可能足以影響資料存取的複雜度以及程式碼閱讀的難易度,所以我們可以認識一下究竟兩者有什麼不同的地方:

  • 鍵值的類型:

    在物件內的鍵值(或屬性名稱) 必須是字串或是 Symbol。而在 Map 內,鍵值可以是任何型別,這包含任何其他的物件或是陣列 。你當然可以試試看用物件來當作另外一個物件的屬性名稱,不過這個物件會被 JS 強制轉型變成 [object Object] 而變成另外一個字串屬性。

      let o = {} 
      let anotherObj = {} 
      o[anotherObj] = 'anotherObject' // {'[object Object]' : anotherObject}
      
      let theThirdObj = {} 
      o [theThirdObj] = 'theThirdObj' //  {'[object Object]' : anotherObject}
    
  • 元素的順序,在 Map 裡面,元素被新增進去之後,順序就會被固定下來。而在 Object 內則無法保證。

  • 繼承關係:Map 繼承於物件 ( Object ) ,而反過來則否,因此在 Map 上那些方便的方法,在 Object 上無法使用。

      let newMap = new Map()
      console.log(newMap instanceof Object) //true
      console.log(Object instanceof newMap) //false
    
  • 可被巡訪:這大概是最大的差別了,因為一般物件上並沒有提供可以直接巡訪的方法,只能透過 for .. in 迴圈達成,或是必須透過 Object.keys 方法把屬性轉為陣列,但是在陣列 、 Set 跟 Map 上都有 forEach 方法可以直接對裡面的元素做巡訪。

Map 與 Object 使用時機

Map 在操作元素上雖然提供了許多語意化的方法,但有時候我們還是會需要像一般物件那樣方便新增元素的方式,最後我們就來看看兩者各適合怎樣的使用情境:

  • 屬性值:這也是兩種型別最大的差別。在知道屬性值都單純只是字串時,使用一般物件就好,因為 Map 雖然可以儲存任何型別的數值,但是因為使用函式建構子創造物件,且在新增、修改元素時必須透過 getset 函式幫忙,因此速度上會比單純使用物件還要慢。
  • JSON 格式:在需要以 JSON 格式來進行開發作業時,選擇一般物件。因為 JS 內的物件可以很直接的被轉為 JSON 格式,這在進行 API 溝通時非常好用。
  • 順序性: 在 Map 內的元素順序會被保留,因此在處理資料時,如果維持順序的穩定很重要,就可以考慮使用 Map
  • 需要一些特定功能:有時候我們會需要某個函式來取得其他屬性資訊,物件因為存取方便的關係,在物件內的屬性如果是函式,就可以直接被執行,Map 就比較麻煩。

總結

除了前面我們提到的幾個基本資料結構,今天我們又認識了 JS 內新的 Map 跟 Set 兩種新的資料型別。在資料結構選擇上永遠是根據你的需求而定,雖然用簡單的物件或陣列組合或許就可以達到,多認識一些這樣子的資料結構不一定會大幅度增加開發速度,但絕對會讓你在開發時有更多其他潛在更好的選擇來達成你的需求。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×