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(例項化傳參)
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
的一種方式.
- 方法2 (使用
Map
的 set 方法)
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