[筆記] 理解 Map(映射) & WeakMap(弱映射)

Map


  • 傳統上我們使用 Object 建立 key/value-pair 資料結構,來模擬 maps。
    但缺點是無法使用非字串值的key。ES6的Map就解決這樣的問題了!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const m = {};
    const x = {id:1},
    y = {id:2};

    m[x] = "foo"; // 這裡的key變成 [object Objec]
    m[y] = "bar"; // 這裡的key變成 [object Objec]

    m[x]; // bar
    m[y]; // bar
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const m = new Map();
    const x = {id:1},
    y = {id:2};

    m.set(x,"foo");
    m.set(y,"bar");

    m.get(x); // foo
    m.get(y); // bar
  • set(key,value)

    • 替 Map 新增一個鍵/值並回傳該Map
    • key 可以是任何型態
    • 如果key已經存在,則會蓋掉原有的值
    • set方法會返回該Map,所以可以一直串接

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const m = new Map();

      m.set('edition', 6)
      m.set(262, 'standard')
      m.set(undefined, 'nah')

      let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
  • get(key)

    • 讀取對應key的鍵值,若找不到回傳 undefined
    1
    2
    3
    4
    5
    const m = new Map();
    const func = ()=>{console.log('Hello World!')};
    m.set(func,'Hello Map');
    m.get(func) //Hello Map
    m.get('hi') // undefined
  • size

    • 返回Map成員的總數量 new Map().size ;
  • delete(key)

    • 刪除key的某個值,返回布林值,true代表刪除成功

      1
      2
      3
      4
      5
      6
      const m = new Map();
      m.set(undefined, 'nah');
      m.has(undefined) // true

      m.delete(undefined)
      m.has(undefined) // false
  • has(key)

    • 返回布林值,判斷某個key是否存在Map中

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const m = new Map();

      m.set('edition', 6);
      m.set(262, 'standard');
      m.set(undefined, 'nah');

      m.has('edition') // true
      m.has('years') // false
      m.has(262) // true
      m.has(undefined) // true
  • clear

    • 刪除Map所有值,不會返回值 new Map().clear() ;

Map 迭代

  1. keys()
  2. values()
  3. entries()
  4. forEach()
  5. Map迭代順序是依照加入的順序
  6. Map 結構的默認遍歷器接口(Symbol.iterator屬性),就是entries方法

    1
    map[Symbol.iterator] === map.entries // true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    const map = new Map([
    ['F', 'no'],
    ['T', 'yes'],
    ]);

    for (let key of map.keys()) {
    console.log(key);
    }
    // "F"
    // "T"

    for (let value of map.values()) {
    console.log(value);
    }
    // "no"
    // "yes"

    for (let item of map.entries()) {
    console.log(item[0], item[1]);
    }
    // "F" "no"
    // "T" "yes"

    // 或者
    for (let [key, value] of map.entries()) {
    console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"

    // 等同於使用map.entries()
    for (let [key, value] of map) {
    console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
  • Map 轉為陣列幾種方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const map = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
    ]);

    [...map.keys()]
    // [1, 2, 3]

    [...map.values()]
    // ['one', 'two', 'three']

    [...map.entries()]
    // [[1,'one'], [2, 'two'], [3, 'three']]

    [...map]
    // [[1,'one'], [2, 'two'], [3, 'three']]
  • forEach
    與陣列forEach相似


WeekSet

  • WeekSetmap 外部行為大致相同
  • 只接受物件作為鍵值,若物件本身被GC了,WeekSet中的條目(entry)也會被移除
  • 沒有size特性,也沒有clear()方法,也沒有提供任何迭代方法
  • 承上WeakMap只有四個方法可用:get()、set()、has()、delete()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const m = new WeekMap();

    let x = {id:1},
    y = {id:2};

    m.set(x,"foo");

    m.has(x); // true
    m.has(y); // false
  • 如果作為map鍵值的物件會被刪除或可能被GC,那麼WeekMap會是更好的選擇

  • WeakMap 的用途例子

    • DOM 作為鍵名

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
        let myElement = document.getElementById('logo');
      let myWeakmap = new WeakMap();

      myWeakmap.set(myElement, {timesClicked: 0});

      myElement.addEventListener('click', function() {
      let logoData = myWeakmap.get(myElement);
      logoData.timesClicked++;
      }, false);
      /*一旦這個 DOM 節點刪除,該狀態就會自動消失,不存在內存洩漏風險*/
    • 私有屬性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
        const _counter = new WeakMap();
      const _action = new WeakMap();

      class Countdown {
      constructor(counter, action) {
      _counter.set(this, counter);
      _action.set(this, action);
      }
      dec() {
      let counter = _counter.get(this);
      if (counter < 1) return;
      counter--;
      _counter.set(this, counter);
      if (counter === 0) {
      _action.get(this)();
      }
      }
      }

      const c = new Countdown(2, () => console.log('DONE'));

      c.dec()
      c.dec() // DONE
      /* 一旦c被刪除,Countdown 內部的 _counter和_action也就隨之消失,不會造成內存洩漏 */

參考資料來源

  1. Set 和 Map 数据结构
  2. MDN - Map
  3. MDN - WeakMap