首頁 > 軟體

JavaScript 中有了Object 為什麼還需要 Map 呢

2021-09-27 13:03:30

一、別把物件當 Map

1、可能通過原型鏈存取到未定義的屬性

假設現有場景,開發一個網站,需要提供日語、漢語、韓語三種語言,我們可以定義一個字典去管理。

const dictionary = {
    'ja': {
        'Ninjas for hire': '忍者を僱う',
    },
    'zh': {
        'Ninjas for hire': '忍者出租',
    },
    'ko': {
        'Ninjas for hire': '고용 닌자',
    }
}

console.log(dictionary.ja['Ninjas for hire']) // 忍者を僱う
console.log(dictionary.zh['Ninjas for hire']) // 忍者出租
console.log(dictionary.ko['Ninjas for hire']) // 고용 닌자

這樣我們就把不同語言的字典管理起來了。但是,當我們試圖存取 constroctor 屬性,問題就出現了。

console.log(dictionary.ko['constructor']) // ƒ Object() { [native code] }

對於不存在的屬性,我們期望得到 undefined,結果卻通過原型鏈存取到了未定義的屬性,原型物件的 constructor 屬性,指向建構函式。

此處有一個解決辦法是把原型設定為 null

Object.setPrototypeOf(dictionary.ko, null)
console.log(dictionary.ko['constructor']) // undefined

2、物件的 Key 只能是字串

假設需要將物件的 key 對映為 html 節點。我們寫如下程式碼:

/* html部分
<div id="firstElement"></div>
<div id="secondElement"></div>
*/

const firstElement = document.getElementById('firstElement')
const secondElement = document.getElementById('secondElement')

const map = {}

map[firstElement] = {
    data: 'firstElement'
}
map[secondElement] = {
    data: 'secondElement'
}

console.log(map[firstElement].data)     // secondElement
console.log(map[secondElement].data)    // secondElement

第一個元素的資料被覆蓋了,原因是物件中的 key 只能是字串型別,當我們沒有使用字串型別時,它會隱式呼叫 toString() 函數進行轉換。於是兩個 html 元素都被轉為字串 [object HTMLDivElement]

物件的鍵也可以為 Symbol,不過在 for..in 遍歷和 Object.keys() 以及用 JSON.stringify() 進行序列化的時候,都會忽略為 Symbol 的鍵。

二、使用 Map

1、Map 常用操作

Map 可以使用任何 JavaScript 資料型別作為鍵

function People(name) {
    this.name = name
}
const zhangsan = new People('zhangsan')
const xiaoming = new People('xiaoming')
const lihua = new People('lihua')
// 建立 Map
const map = new Map()
// 建立 Map 並進行初始化 將二維鍵值對陣列轉換成一個Map物件
const map1 = new Map([
    ['key1', 'val1'],
    ['key2', 'val2'],
])
// 將 Map 轉為二維陣列
console.log(Array.from(map1)) // [ [ 'key1', 'val1' ], [ 'key2', 'val2' ] ]
// 設定鍵值對映關係
map.set(zhangsan, { region: 'HB' })
map.set(xiaoming, { region: 'HN' })
// 根據 key 獲取對應值
console.log(map.get(zhangsan)) // { region: 'HB' }
console.log(map.get(xiaoming)) // { region: 'HN' }
// 獲取不存在的 key 得到 undefined
console.log(map.get(lihua))     // undefined
// 通過 has 函數判斷指定 key 是否存在
console.log(map.has(lihua))     // false
console.log(map.has(xiaoming))  // true
// map儲存對映個數
console.log(map.size)           // 2
// delete 刪除 key
map.delete(xiaoming)
console.log(map.has(xiaoming))  // false
console.log(map.size)           // 1
// clear 清空 map
map.clear()
console.log(map.size)           // 0

2、遍歷 Map

Map 可以確保遍歷的順序和插入的順序一致

const zhangsan = { name: 'zhangsan' }
const xiaoming = { name: 'xiaoming' }
const map = new Map()
map.set(zhangsan, { region: 'HB' })
map.set(xiaoming, { region: 'HN' })
// 每個鍵值對返回的是 [key, value] 的陣列
for (let item of map) { // = for (let item of map.entries()) {
    console.log(item)
    // [ { name: 'zhangsan' }, { region: 'HB' } ]
    // [ { name: 'xiaoming' }, { region: 'HN' } ]
}
// 遍歷 key
for (let key of map.keys()) {
    console.log(key)
    // { name: 'zhangsan' }
    // { name: 'xiaoming' }
}
// 遍歷 value
for (let key of map.values()) {
    console.log(key)
    // { region: 'HB' }
    // { region: 'HN' }
}
// 使用 forEach() 方法迭代 Map
map.forEach(function(value, key) {
    console.log(key, value)
    // { name: 'zhangsan' } { region: 'HB' }
    // { name: 'xiaoming' } { region: 'HN' }
})

3、Map 中判斷 key 相等

Map 內部使用 SameValueZero 比較操作。

關於SameValue SameValueZero

SameValue (Object.is()) 和嚴格相等(===)相比,對於 NaN 和 +0,-0 的處理不同

Object.is(NaN, NaN) // true
Object.is(0, -0) // false

SameValueZero SameValue 的區別主要在於 0 與 -0 是否相等。

map.set(NaN, 0)
map.set(0, 0)
console.log(map.has(NaN))   // true
console.log(map.has(-0))    // true

4、複製或合併 Map

Map 能像陣列一樣被複制

let original = new Map([
    [1, {}]
])
let clone = new Map(original) // 克隆 Map

console.log(clone.get(1)); // {}
console.log(original === clone) // false
console.log(original.get(1) === clone.get(1)) // true

多個 Map 合併

let first = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
]);
let second = new Map([
    [1, 'uno'],
    [2, 'dos']
]);
// 合併兩個 Map 物件時,如果有重複的鍵值,則後面的會覆蓋前面的。
// 展開運運算元本質上是將 Map 物件轉換成陣列。
let merged = new Map([...first, ...second]);

console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

5、Map 序列化

Map 無法被序列化,如果試圖用 JSON.stringify 獲得 Map 的 JSON 的話,只會得到 "{}"。

由於 Map 的鍵可以是任意資料型別,而 JSON 僅允許將字串作為鍵,所以一般情況下無法將 Map 轉為 JSON。

不過可以通過下面的方式去嘗試序列化一個 Map:

// 初始化 Map(1) {"key1" => "val1"}
const originMap = new Map([['key1', 'val1']])
// 序列化 "[["key1","val1"]]"
const mapStr = JSON.stringify(Array.from(originMap.entries())) 
// 反序列化 Map(1) {"key1" => "val1"}
const cloneMap = new Map(JSON.parse(mapStr))

三、Map 和 Object 的效能差異

記憶體佔用

不同瀏覽器的情況不同,但給定固定大小的記憶體,Map 大約可以比 Object 多儲存 50% 的鍵/值對。

插入效能

Map 略快,如果涉及大量操作,建議使用 Map

查詢速度

效能差異極小,但如果只包含少量鍵/值對,則 Object 有時候速度更快。Object 作為陣列使用時瀏覽器會進行優化。如果涉及大量查詢操作,選擇 Object 會更好一些。

刪除效能

如果程式碼涉及大量的刪除操作,建議選擇 Map

到此這篇關於JavaScript 中有了Object 為什麼還需要 Map 呢的文章就介紹到這了,更多相關JavaScript  Map 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com