1. 程式人生 > 實用技巧 >Map 和 Set 兩種資料結構在 ES6 的作用

Map 和 Set 兩種資料結構在 ES6 的作用

文字轉載自https://mp.weixin.qq.com/s/T0RbksuupBF6oKJEl-ijRg

如果要用一句來描述,我們可以說

Set是一種叫做集合的資料結構,Map是一種叫做字典的資料結構

那什麼是集合?什麼又是字典呢?

  • 集合

集合,是由一堆無序的、相關聯的,且不重複的記憶體結構【數學中稱為元素】組成的組合

  • 字典

字典(dictionary)是一些元素的集合。每個元素有一個稱作key 的域,不同元素的key 各不相同

它們之間又有什麼區別呢?

  • 共同點:集合、字典都可以儲存不重複的值
  • 不同點:集合是以[值,值]的形式儲存元素,字典是以[鍵,值]的形式儲存

背景

大多數主流程式語言都有多種內建的資料集合。例如Python

擁有列表(list)、元組(tuple)和字典(dictionary),Java有列表(list)、集合(set)、佇列(queue

然而 JavaScript 直到ES6的釋出之前,只擁有陣列(array)和物件(object)這兩個內建的資料集合

ES6 之前,我們通常使用內建的 Object 模擬Map

但是這樣模擬出來的map會有一些缺陷,如下:

  1. Object的屬性鍵是StringSymbol,這限制了它們作為不同資料型別的鍵/值對集合的能力
  2. Object不是設計來作為一種資料集合,因此沒有直接有效的方法來確定物件具有多少屬性

Set

定義: Set 物件允許你儲存任何型別的唯一值,無論是原始值或者是物件引用,Set

物件是值的集合,你可以按照插入的順序迭代它的元素。Set中的元素只會出現一次,即 Set 中的元素是唯一的

Set本身是一個建構函式,用來生成 Set 資料結構

基本使用

  • 語法
    new Set([iterable]) 接收一個數組(或者具有 iterable 介面的其他資料結構), 返回一個新的Set物件
const set = new Set([1,2,1,2])
console.log(set) // {1,2} 

上面程式碼可以看出 Set 是可以去除陣列中的重複元素

屬性及方法

屬性

  • size: 返回集合中所包含的元素的數量
console.log(new Set([1,2,1,2]).size) //
2

操作方法

  • add(value): 向集合中新增一個新的項
  • delete(value): 從集合中刪除一個值
  • has(value): 如果值在集合中存在,返回ture, 否則返回false
  • clear(): 移除集合中的所有項
let set = new Set()
set.add(1)
set.add(2)
set.add(2)
set.add(3)
console.log(set) // {1,2,3}
set.has(2) // true
set.delete(2)  
set.has(2) // false
set.clear() 

遍歷方法

  • keys(): 返回鍵名的遍歷器
  • values(): 返回鍵值的遍歷器
  • entries(): 返回鍵值對的遍歷器
  • forEach(): 使用回撥函式遍歷每個成員
let set = new Set([1,2,3,4])

// 由於set只有鍵值,沒有鍵名,所以keys() values()行為完全一致
console.log(Array.from(set.keys())) // [1,2,3,4]
console.log(Array.from(set.values())) // [1,2,3,4]
console.log(Array.from(set.entries())) //  [[1,1],[2,2],[3,3],[4,4]]

set.forEach((item) => { console.log(item)}) // 1,2,3,4

應用場景

因為 Set 結構的值是唯一的,我們可以很輕鬆的實現以下

// 陣列去重
let arr = [1, 1, 2, 3];
let unique = [... new Set(arr)];

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
    
// 並集
let union = [...new Set([...a, ...b])]; // [1,2,3,4]
    
// 交集
let intersect = [...new Set([...a].filter(x => b.has(x)))]; [2,3]
    
// 差集
let difference = Array.from(new Set([...a].filter(x => !b.has(x)))); [1]

WeakSet

WeakSet 物件是一些物件值的集合, 並且其中的每個物件值都只能出現一次。在WeakSet的集合中是唯一的

WeakSet 的出現主要解決弱引用物件儲存的場景, 其結構與Set類似

Set的區別

  • 與Set相比,WeakSet 只能是物件的集合,而不能是任何型別的任意值
  • WeakSet集合中物件的引用為弱引用。如果沒有其他的對WeakSet中物件的引用,那麼這些物件會被當成垃圾回收掉。這也意味著WeakSet中沒有儲存當前物件的列表。正因為這樣,WeakSet 是不可列舉的

WeakSet 的屬性跟操作方法與 Set 一致,不同的是 WeakSet 沒有遍歷方法,因為其成員都是弱引用,弱引用隨時都會消失,遍歷機制無法保證成員的存在

上面一直有提到弱引用,那弱引用到底是指什麼呢?

弱引用是指不能確保其引用的物件不會被垃圾回收器回收的引用,換句話說就是可能在任意時間被回收

Map

Map 物件儲存鍵值對,並且能夠記住鍵的原始插入順序。任何值(物件或者原始值) 都可以作為一個鍵或一個值。一個Map物件在迭代時會根據物件中元素的插入順序來進行 — 一個 for...of 迴圈在每次迭代後會返回一個形式為[key,value]的陣列

基本使用

  • 語法

new Map([iterable]) Iterable 可以是一個數組或者其他 iterable 物件,其元素為鍵值對(兩個元素的陣列,例如: [[ 1, 'one' ],[ 2, 'two' ]])。每個鍵值對都會新增到新的 Map

let map = new Map()
map.set('name', 'vuejs.cn');
console.log(map.get('name'))

屬性及方法

基本跟 Set 類似,同樣具有如下方法屬性

  • size: 返回 Map 結構的元素總數
let map = new Map()
map.set('name', 'vuejs.cn');
console.log(map.size) // 1

console.log(new Map([['name','vue3js.cn'], ['age','18']]).size) // 2

操作方法

  • set(key, value): 向 Map 中加入或更新鍵值對
  • get(key): 讀取 key 對用的值,如果沒有,返回 undefined
  • has(key): 某個鍵是否在 Map 物件中,在返回 true 否則返回 false
  • delete(key): 刪除某個鍵,返回 true, 如果刪除失敗返回 false
  • clear(): 刪除所有元素
let map = new Map()
map.set('name','vue3js.cn')
map.set('age','18')
console.log(map) // Map {"name" => "vuejs.cn", "age" => "18"}
map.get('name') // vue3js.cn 
map.has('name') // true
map.delete('name')  
map.has(name) // false
map.clear() // Map {} 

遍歷方法

  • keys():返回鍵名的遍歷器
  • values():返回鍵值的遍歷器
  • entries():返回所有成員的遍歷器
  • forEach():遍歷 Map 的所有成員
let map = new Map()
map.set('name','vue3js.cn')
map.set('age','18')

console.log([...map.keys()])  // ["name", "age"]
console.log([...map.values()])  // ["vue3js.cn", "18"]
console.log([...map.entries()]) // [['name','vue3js.cn'], ['age','18']]

// name vuejs.cn
// age 18
map.forEach((value, key) => { console.log(key, value)}) 

應用場景

Map 會保留所有元素的順序, 是在基於可迭代的基礎上構建的,如果考慮到元素迭代或順序保留或鍵值型別豐富的情況下都可以使用,下面摘抄自 vue3 原始碼中依賴收集的核心實現

let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
    ...
  }

WeakMap

WeakMap 物件是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是物件,而值可以是任意的

Map的區別

  • Map 的鍵可以是任意型別,WeakMap 的鍵只能是物件型別
  • WeakMap 鍵名所指向的物件,不計入垃圾回收機制

WeakMap 的屬性跟操作方法與 Map 一致,同 WeakSet 一樣,因為是弱引用,所以 WeakSet 也沒有遍歷方法

型別的轉換

  • Map 轉為 Array
// 解構
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log([...map]) // [[1, 1], [2, 2], [3, 3]]

// Array.from()
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log(Array.from(map)) // [[1, 1], [2, 2], [3, 3]]
  • Array 轉為 Map
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log(map) // Map {1 => 1, 2 => 2, 3 => 3}
  • Map 轉為 Object
// 非字串鍵名會被轉換為字串
function mapToObj(map) {
    let obj = Object.create(null)
    for (let [key, value] of map) {
        obj[key] = value
    }
    return obj
}
const map = new Map().set('name', 'vue3js.cn').set('age', '18')
mapToObj(map)  // {name: "vue3js.cn", age: "18"}
  • Object 轉為 Map
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj))

總結

  • Set、Map、WeakSet、WeakMap、都是一種集合的資料結構
  • Set、WeakSet 是[值,值]的集合,且具有唯一性
  • Map 和 WeakMap 是一種[鍵,值]的集合,Map 的鍵可以是任意型別,WeakMap 的鍵只能是物件型別
  • Set 和 Map 有遍歷方法,WeakSet 和 WeakMap 屬於弱引用不可遍歷

參考文獻

  • https://zh.wikipedia.org/wiki/%E5%BC%B1%E5%BC%95%E7%94%A8
  • https://developer.mozilla.org/
  • https://es6.ruanyifeng.com/