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);

参考资料