ES6 之後加入兩種新的資料結構:Map 跟 Set 。 Map 與 Set 都是像字串跟陣列這樣可以被尋訪的類型,也就是說可以使用 for 迴圈去一個一個查找跟操作他們的值。今天就來說明一下這兩個類別跟使用方式吧!
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 雖然可以儲存任何型別的數值,但是因為使用函式建構子創造物件,且在新增、修改元素時必須透過
get
、set
函式幫忙,因此速度上會比單純使用物件還要慢。 - JSON 格式:在需要以 JSON 格式來進行開發作業時,選擇一般物件。因為 JS 內的物件可以很直接的被轉為 JSON 格式,這在進行 API 溝通時非常好用。
- 順序性: 在
Map
內的元素順序會被保留,因此在處理資料時,如果維持順序的穩定很重要,就可以考慮使用Map
。 - 需要一些特定功能:有時候我們會需要某個函式來取得其他屬性資訊,物件因為存取方便的關係,在物件內的屬性如果是函式,就可以直接被執行,
Map
就比較麻煩。
總結
除了前面我們提到的幾個基本資料結構,今天我們又認識了 JS 內新的 Map 跟 Set 兩種新的資料型別。在資料結構選擇上永遠是根據你的需求而定,雖然用簡單的物件或陣列組合或許就可以達到,多認識一些這樣子的資料結構不一定會大幅度增加開發速度,但絕對會讓你在開發時有更多其他潛在更好的選擇來達成你的需求。