Map

Node.js Map

Map 資料是 key / value 方式紀錄

  • object 的 key 只能是 字串 (string),Map 的 key 則可以是 任何 的資料型態
  • Map 中的資料是有序的,當你遍歷一個 Map 資料結構時,會依照 key-value pairs 先前寫入的順序 (insertion order)。

函式資料

變數 說明
Map.size 元素數量
Map.has() 是否有此鍵值資料
Map.get() 取得鍵值資料
Map.set() 設定鍵值資料
Map.delete() 刪除鍵值資料
Map.clear() 刪除全部資料
Map.keys() 取得全部鍵值資料
Map.values() 取得全部數值資料
Map.forEach() 遍歷 Map 資料
Map.entries() 取得 [key, value] 資料

Map 允許使用函數、物件、其他類型作為 key 值

set(key, value)

const func = () => null;
const object = {};
const array = [];
const bool = false;
const map = new Map();

map.set(func, 'value1');
map.set(object, 'value2');
map.set(array, 'value3');
map.set(bool, 'value4');
map.set(NaN, 'value5');

// Map(5) {
//     [Function: func] => 'value1',
//         {} => 'value2',
//         [] => 'value3',
//         false => 'value4',
//         NaN => 'value5'
// }
console.log(map);

遍歷元素

物件需要透過 Object.keys or Object.values or Object.entries,或者 for..in 去做循環遍歷元素

Map 可以直接遍歷,使用 for..of or forEach

for(let [key, value] of map){
    console.log(key)
    console.log(value)
}

map.forEach((key,value)=>{
    console.log(key)
    console.log(value)
})

Map 與 Object 效率比較

查詢鍵值效率

let obj = {}, map = new Map(), n = 1000000
for(let i = 0 ; i < n ; i++){
    obj[i] = i
    map.set(i, i)
}

let result;
console.time('Object');
result = obj.hasOwnProperty('999999');
console.timeEnd('Object');
// Object: 0.250ms

console.time('Map');
result = map.has(999999);
console.timeEnd('Map');
// Map: 0.095ms (2.6 times faster)

新增效率

console.time('Object');
obj[n] = n;
console.timeEnd('Object');
// Object: 0.229ms

console.time('Map');
map.set(n, n);
console.timeEnd('Map');
// Map: 0.005ms (45.8 times faster!)

刪除效率

console.time('Object');
delete obj[n];
console.timeEnd('Object');
// Object: 0.376ms

console.time('Map');
map.delete(n);
console.timeEnd('Map');
// Map: 0.012ms (31 times faster!)

for loop 效率

Map在 for loop 下較慢

console.time('Object');
for (let i = 0; i < n; i++) {
  obj[i] = i;
}
console.timeEnd('Object');
// Object: 32.143ms

let obj = {}, map = new Map(), n = 1000000;
console.time('Map');
for (let i = 0; i < n; i++) {
  map.set(i, i);
}
console.timeEnd('Map');
// Map: 163.828ms (5 times slower)

Map 缺點

  • 賦值搜索操作都是 O(n) 的時間複雜度(n 是鍵值對的個數)
    • 因為這兩個操作都需要遍歷全部整個陣列來進行匹配
  • 可能會導致 內存洩漏(Memory Leak),因為陣列會一直引用著每個鍵和值。這種引用使得垃圾回收算法不能回收處理他們,即使沒有其他任何引用存在了。

WeakMap

WeakMap 特性

  • 只接受 object 當作鍵值(key),null 也不能當鍵值
  • 基本資料型態 (primitive data types) 都不能被當作是 key
  • WeakMap 中的 key 所指向的 object 不會被垃圾回收機制 (garbage collection) 計入參考
    • weak: 弱引用 (weakly reference)
  • WeakMap 中的 object 可能隨時會被自動回收 (garbage collected),而當 object 被回收後,其所對應的 key-value pair 也會自動被刪除
  • value 可以是任何型態,包含像 object 或 function
  • key 和 value 可以是任何物件,甚至是個 WeakMaps
var wm = new WeakMap();

// 錯誤
// TypeError: Invalid value used as weak map key
wm.set('a', 1);

// 錯誤
// TypeError: Invalid value used as weak map key
wm.set(101, 2);
const wm1 = new WeakMap(),
   wm2 = new WeakMap()
const o1 = {},
   o2 = function() {};

wm1.set(o1, 123);
wm1.set(o2, 'abc');
// value 可以是任何型態,包含像 object 或 function
wm2.set(o1, o2);
// key 和 value 可以是任何物件,甚至是個 WeakMaps
wm2.set(wm1, wm2);

WeakMap 使用情境

  • WeakMap 的典型運用之一是引用 DOM 元素物件,當 DOM 元素被移除後,對應的 WeakMap 紀錄也會自動被移除,所以說用 WeakMap 可以方便地避免內存泄露 (memory leak) 的問題。

使用 DOM 當作 WeakMap 的 key

let myBtn = document.getElementById('btn');

let wm = new WeakMap();
wm.set(myBtn, {clickCnt: 0});

myBtn.addEventListener('click', function() {

  let btnData = wm.get(myBtn);
  btnData.clickCnt++;

}, false);

參考資料