◆

JavaScript WeakMap 与 Map 的区别详解

2023-02-02 01:370

什么是 WeakMap

WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。

WeakMap 可以使用 set 方法添加成员

const wm1 = new WeakMap();
const key = { foo: 1 };
wm1.set(key, 2);
wm1.get(key); // 2

WeakMap 也可以接受一个数组,作为构造函数的参数

const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([
  [k1, 'foo'],
  [k2, 'bar'],
]);
wm2.get(k2); // "bar"

WeakMap 与 Map 的区别

首先,WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。

const map = new WeakMap();
// TypeError: 1 is not an object!
map.set(1, 2);

// TypeError: Invalid value used as weak map key
map.set(Symbol(), 2);

// TypeError: Invalid value used as weak map key
map.set(null, 2);

上面代码中,如果将数值 1 和 Symbol 值作为 WeakMap 的键名,都会报错。 其次,WeakMap 的键名所指向的对象,不计入垃圾回收机制。

作用

WeakMap 的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo 元素'],
  [e2, 'bar 元素'],
];

上面代码中,e1 和 e2 是两个对象,我们通过 arr 数组对这两个对象添加一些文字说明。这就形成了 arr 对 e1 和 e2 的引用。 一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放 e1 和 e2 占用的内存。

// 不需要 e1 和 e2 的时候
// 必须手动删除引用
arr[0] = null;
arr[1] = null;

上面这样的写法显然很不方便。一旦忘了写,就会造成内存泄露。

WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。 因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要, WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

示例

const wm = new WeakMap();

let key = {};
let obj = 'value';
wm.set(key, obj);
key = null;
console.log(wm);

如上述代码,我们在浏览器上测试一下,我们将 key 这个引用主动设置为 null 时,浏览器的垃圾回收机制发现 WeakMap 的 key 引用消失时,会自动的将此键值对给回收。

weakMap

tip

WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

const wm = new WeakMap();
let key = {};
let obj = { foo: 1 };

wm.set(key, obj);
obj = null;
wm.get(key);
// Object {foo: 1}

上面代码中,键值 obj 是正常引用。所以,即使在 WeakMap 外部消除了 obj 的引用,WeakMap 内部的引用依然存在。

WeakMap 的语法

WeakMap 与 Map 在 API 上的区别主要是两个

  • 没有遍历操作(即没有 keys()、values() 和 entries() 方法),也没有 size 属性。 因为没有办法列出所有键名,某个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。 这一刻可以取到键名,下一刻垃圾回收机制突然运行了,这个键名就没了,为了防止出现不确定性,就统一规定不能取到键名。
  • 无法清空,即不支持 clear 方法。因此,WeakMap 只有四个方法可用:get()、set()、has()、delete()。
const wm = new WeakMap();

// size、forEach、clear 方法都不存在
wm.size; // undefined
wm.forEach; // undefined
wm.clear; // undefined

WeakMap 的用途

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

上面代码中,myElement 是一个 DOM 节点,每当发生 click 事件,就更新一下状态。 我们将这个状态作为键值放在 WeakMap 里,对应的键名就是 myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

  • WeakMap 的另一个用处是部署私有属性。
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

上面代码中,Countdown 类的两个内部属性 _counter 和 _action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEJavaScript WeakMap 与 Map 的区别详解加载中