ES6 Map 資料結構

閃現A小兵發表於2020-02-25

js物件本質上是鍵值對的集合,但是隻能用字串作為鍵,雖然在定義的時候可以使用Number或者Boolean型別作為鍵名,但是卻會改變它的型別

const obj = {
  true:'value1',
  2:'value2'
}
console.log(Object.keys(obj))  // ["2", "true"]
複製程式碼

發現 obj 這個物件的key已經全部都變成了字串,那麼,當我們不想要改變這個key的型別的時候,有沒有什麼辦法呢?有的,那就是今天我們的主人公 Map 以及它的兄弟 WeakMap

Map先來

話不多說,先來一波程式碼,直接copy到瀏覽器上去看效果.

const keyName = {name:'key'}
const arr = [
  ['name','zhangsan'],
  ['age',18],
  [keyName,'kkk']
]
const map0 = new Map(arr)
console.log(map0)  // {"name" => "zhangsan", "age" => 18, {…} => "kkk"}

const map = new Map()
map.set('name','zhangsan')
console.log(map)  // Map(1) {"name" => "zhangsan"}
map
  .set('prop1','value1')
  .set('prop2','value')
  .set('prop2','value2')
console.log(map)  // Map(3) {"name" => "zhangsan", "prop1" => "value1", "prop2" => "value2"}
console.log(map.has('prop2'))  // true
console.log(map.has('prop3'))  // false
console.log(map.get('prop2'))  // value2
console.log(map.get('prop3'))  // undefined

console.log(map.size)  // 3
let isSuccess = map.delete('prop2')
console.log(isSuccess)  // true
let isSuccess2 = map.delete('prop3')
console.log(isSuccess2)  // false

console.log(map.size)  // 2
let s = map.clear()
console.log(map.size) // 0
複製程式碼

可以看出Map是一種資料結構.類似於物件,也是鍵值對的集合,但是鍵名的範圍更加的廣闊,所有的資料型別都可以. 下面我們來一段一段分析以上的程式碼:

生成一個map

  1. 方法1(例項化傳參)
const keyName = {name:'key'}
const arr = [
  ['name','zhangsan'],
  ['age',18],
  [keyName,'kkk']
]
const map0 = new Map(arr)
console.log(map0)  // {"name" => "zhangsan", "age" => 18, {…} => "kkk"}
複製程式碼

這裡是將一個二維陣列當作引數在 Map例項化的時候傳入,這是設定Map的一種方式.

  1. 方法2 (使用Mapset 方法)
const map = new Map()
map.set('name','zhangsan')
console.log(map)  // Map(1) {"name" => "zhangsan"}
map
  .set('prop1','value1')
  .set('prop2','value')
  .set('prop2','value2')
console.log(map)  // Map(3) {"name" => "zhangsan", "prop1" => "value1", "prop2" => "value2"}
複製程式碼

這裡還可以看出 Map還支援鏈式呼叫,因為set 方法返回的是當前的Map物件

判斷map中是否有某個屬性

console.log(map.has('prop2'))  // true
console.log(map.has('prop3'))  // false
複製程式碼

獲取map中的某個屬性值

console.log(map.get('prop2'))  // value2
console.log(map.get('prop3'))  // undefined
複製程式碼

獲取map中的長度

console.log(map.size)  // 3
複製程式碼

刪除map中的鍵值對

let isSuccess = map.delete('prop2')
console.log(isSuccess)  // true
let isSuccess2 = map.delete('prop3')
console.log(isSuccess2)  // false
複製程式碼

返回一個Boolean型別的值,如果原本存在這個屬性,那麼刪除成功後返回 true,否則返回 false 此時再執行一下如下程式碼,發現結果已經變成2了,說明我們已經成功刪除了一個鍵值對

console.log(map.size)  // 2
複製程式碼

清除map所有的鍵值對

let s = map.clear()
console.log(map.size) // 0
複製程式碼

呼叫 clear 方法後再次檢視map的長度,發現就變成0了

Q&A:是否看上去長的一樣的就是同一個鍵值對?

map.set([1],1)
console.log(map.get([1]))  // undefined
const arr2 = [2]
map.set(arr2,1)
console.log(map.get(arr2))  // 1
複製程式碼

由此可以看出,只有對同一個物件的引用,Map結構才將其視為同一個鍵, 上面中的 [1] 看似值是相同的,但是記憶體地址是不一樣的.所以,Map的鍵實際上是和記憶體地址繫結的,不同的記憶體地址視為不同的鍵

總結:Map的方法和屬性

方法
map.set(key,value)
map.get(key)
map.has(key)
map.delete(key)
map.clear()
複製程式碼
屬性
map.size
複製程式碼

Map的3個遍歷器生成函式和1個遍歷方法

const map = new Map([
  ['name','zhangsan'],
  ['age',18]
])
複製程式碼
keys()
for (let key of map.keys()){
  console.log(key)
}
// 'name'
// 'age'
複製程式碼
values()
for (let value of map.values()){
  console.log(value)
}
// 'zhangsan'
// 18
複製程式碼
entries()
for (let item of map.entries()){
  console.log(item)
}
// ["name", "zhangsan"]
// ["age", 18]
複製程式碼
for (let item of map){
  console.log(item)
}
// ["name", "zhangsan"]
// ["age", 18]
複製程式碼

上面最後兩段程式碼可以看出 Map結構的預設遍歷器介面就是 entries 方法

forEach() (map 本身沒有map和filter方法)
map.forEach((value, key, map) => {
  console.log('key: %s, value: %s', key, value)
})
// key: name, value: zhangsan
// key: age, value: 18
複製程式碼

與其他資料結構的互轉

  • Map => Array
const arr = [...map]
console.log(arr)  // [["name", "zhangsan"],["age", 18]]
複製程式碼
  • Array => Map
const map2 = new Map(arr)
console.log(map2)  // Map(2) {"name" => "zhangsan", "age" => 18}
複製程式碼
  • Map => Object
// 如果Map的所有鍵都是字串可以轉為物件
const obj = {}
for(let [k,v] of map2){
  obj[k] = v
}
console.log(obj)  // {name: "zhangsan", age: 18}
複製程式碼
  • Object => Map
let map3 = new Map()
for(let k of Object.keys(obj)){
  map3.set(k,obj[k])
}
console.log(map3)  // Map(2) {"name" => "zhangsan", "age" => 18}
複製程式碼

WeakMap跟上

const wMap = new WeakMap()
const obj = {}
let valueObj = {name:'wang'}
wMap.set(obj,valueObj)
console.log(wMap.get(obj))  // {name: "wang"}
wMap.set('name','lisi')  // Uncaught TypeError: Invalid value used as weak map key
複製程式碼

說明 WeakMap 的鍵不能是除了物件外的其他型別,否則會報錯 注意: WeakMap() 只有4個方法可用: get() set() has() delete().這是因為某個鍵名是否存在是不確定的,和GC有關,所以沒有 size屬性,沒有 clear() 也沒有遍歷的方法

與Map的區別主要有兩點

1.WeakMap的鍵只能是物件(null除外) 2.WeakMap的鍵名所指向的物件不計入垃圾回收機制.即它的鍵名所引用的物件都是弱引用,只要所引用的物件的其他引用都被清除,GC就會釋放該物件所佔用的記憶體,不再需要手動刪除引用

WeakMap的適用範圍也是多樣的,一個典型的例子就是可以註冊監聽事件,好處是一旦DOM物件消失,與之繫結的監聽函式也就自動消失了,不再佔用記憶體

let div1 = document.querySelector('#div1')
const listener = new WeakMap()
function fn(){
  console.log('click')
}
listener.set(div1,fn)
div1.addEventListener('click',listener.get(div1))
複製程式碼

注: 另有一篇文章 ES6 Set 資料結構 講的主要是ES6中的另外一個新的資料結構Set

相關文章