創建Map
(1)使用Map構造函數創建映射對象(可傳入一個可迭代對象,需要包含鍵/值對數組)
const m = new Map() const m1 = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) const m2 = new Map({ [Symbol.iterator]: function* () { yield ['key1', 'val1'] yield ['key2', 'val2'] yield ['key3', 'val3'] } })
映射期待的鍵/值對,無論是否提供
const m3 = new Map([[]]) console.log(m3.has(undefined)) // true console.log(m3.get(undefined)) // undefined
查詢方法
(1)利用 has(key) 方法可查詢是否存在某個鍵
const m = new Map([ ['firstName', 'Matt'] ]) console.log(m.has('firstName')) // true console.log(m.has('lastName')) // false
(2)利用 get(key) 方法可獲取鍵對應的值
const m = new Map([ ['firstName', 'Matt'] ]) console.log(m.get('firstName')) // Matt console.log(m.get('lastName')) // undefined
操作方法
(1)使用 set(key, value) 方法插入鍵/值對,返回映射實例,因此可進行鏈式操作
const m = new Map().set('key1', 'val1') .set('key2', 'val2') .set('key3', 'val3') console.log(m.size) // 3
Map可以使用任何JavaScript數據類型作為鍵
Map中映射的值與Object類似,沒有限制
Map內部使用SameValueZero比較操作(ECMAScript規范內部定義,語言中不能使用),基本上相當於使用嚴格對象相等的標准來檢查鍵的匹配性
const m = new Map() const functionKey = function () {} const symbolKey = Symbol() const objectKey = new Object() m.set(functionKey, 'functionValue') m.set(symbolKey, 'symbolValue') m.set(objectKey, 'objectValue') console.log(m.get(functionKey)) // functionValue console.log(m.get(symbolKey)) // symbolValue console.log(m.get(objectKey)) // objectValue console.log(m.get(function () {})) // undefined
用作鍵和值的對象及其他“集合”類型,在自己的內容或屬性被修改時仍然保持不變
const m = new Map() const objKey = {}, objVal = {}, arrKey = [], arrVal = [] m.set(objKey, objVal) m.set(arrKey, arrVal) objKey.foo = 'foo' objVal.bar = 'bar' arrKey.push('foo') arrVal.push('bar') console.log(m.get(objKey)) // {bar: 'bar'} console.log(m.get(arrKey)) // ['bar]
SameValueZero比較也可能導致意想不到的沖突
const m = new Map() const a = 0 / ' ', b = 0 / ' ', pz = +0, nz = -0 console.log(a === b) // false console.log(pz === nz) // true m.set(a, 'foo') m.set(pz, 'bar') console.log(m.get(a)) // foo console.log(m.get(nz)) // bar
(2)使用size屬性獲取映射中的鍵/值對數量
const m = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) console.log(m.size) // 3
(3)使用 delete() 方法和 clear() 方法刪除值
const m = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) console.log(m.size) // 3 console.log(m.has('key1')) // true m.delete('key1') console.log(m.has('key1')) // false // 清除這個映射實例中所有鍵/值對 m.clear() console.log(m.size) // 0
順序與迭代
Map會維護鍵值對的插入順序
(1)映射實例提供一個迭代器(Iterator),能以插入順序生成[key, value]形式的數組
可以通過 entries() 方法(或者Symbol.iterator屬性,它引用 entries() )取得這個迭代器
const m = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) console.log(m.entries === m[Symbol.iterator]) // true for (let pair of m.entries()) { console.log(pair) } // ['key1', 'val1'] // ['key2', 'val2'] // ['key3', 'val3'] for (let pair of m[Symbol.iterator]()) { console.log(pair) } // ['key1', 'val1'] // ['key2', 'val2'] // ['key3', 'val3']
entries() 是默認迭代器,可以直接對映射實例使用擴展操作,把映射轉換為數組
const m = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) console.log([...m]) // [['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3']]
(2)調用映射的 forEach(callback, opt_thisArg) 方法並傳入回調,依次迭代每個鍵/值對
第一個參數:回調函數(值, 鍵)
第二個參數:回調函數內部this的值
const m = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) m.forEach((val, key) => console.log(`${key} -> ${val}`)) // key1 -> val1 // key2 -> val2 // key3 -> val3
keys() 和 values() 分別返回以插入順序生成鍵和值的迭代器
const m = new Map([ ['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3'] ]) for (let key of m.keys()) { console.log(key) } // key1 // key2 // key3 for (let val of m.values()) { console.log(val) } // val1 // val2 // val3
鍵和值在迭代器遍歷時是可以修改的,但映射內部的引用則無法修改
const m = new Map([ ['key1', 'val1'] ]) for (let key of m.keys()) { key = 'newKey' console.log(key) // newKey console.log(m.get('key1')) // val1 } const keyObj = {id: 1} const m1 = new Map([ [keyObj, 'val1'] ]) // 修改了作為鍵的對象的屬性,但對象在映射內部仍然引用相同的值 for (let key of m1.keys()) { key.id = 'newKey' console.log(key) // {id: 'newKey'} console.log(m1.get(keyObj)) // val1 } console.log(keyObj) // {id: 'newKey'}
選擇Object還是Map
(1)內存占用
Object和Map的工程級實現在不同瀏覽器間存在明顯差異,但存儲單個鍵/值對所占用的內存數量都會隨鍵的數量線性增加。批量添加或刪除鍵/值對則取決於各瀏覽器對該類型內存分配的工程實現。不同瀏覽器的情況不同,但給定固定大小的內存,Map大約可以比Object多存儲50%的鍵/值對
(2)插入性能
向Object和Map中插入新鍵/值對的消耗大致相當,不過插入Map在所有瀏覽器中一般會稍微快一點兒。對這兩個類型來說,插入速度並不會隨着鍵/值對數量而線性增加。如果代碼涉及大量插入操作,那么顯然Map的性能更佳
(3)查找速度
與插入不同,從大型Object和Map中查找鍵/值對的差異極小,但如果只包含少量鍵/值對,則Object有時候速度更快。在把Object當成數組使用的情況下(比如使用連續整數作為屬性),瀏覽器引擎可以進行優化,在內存中使用更高效的布局。這對Map來說是不可能的。對這兩個類型而言,查找速度不會隨着鍵/值對數量增加而線性增加。如果代碼涉及大量查找操作,那么某些情況下可能選擇Object更好一些
(4)刪除性能
使用delete刪除Object屬性的性能一直以來飽受詬病,目前在很多瀏覽器中仍然如此。為此,出現了一些偽刪除對象屬性的操作,包括把屬性設置為undefined或null。但很多時候,這都是一種討厭的或不適宜的折中。而對大多數瀏覽器引擎來說,Map的delete()操作都比插入和查找更快。如果代碼涉及大量刪除操作,那么毫無疑問應該選擇Map
