媒介
我们先从 WeakMap 的特征提及,然后聊聊 WeakMap 的一些运用场景。
特征
1. WeakMap 只接收对象作为键名
const map = new WeakMap();
map.set(1, 2);
// TypeError: Invalid value used as weak map key
map.set(null, 2);
// TypeError: Invalid value used as weak map key
2. WeakMap 的键名所援用的对象是弱援用
这句话实在让我异常费解,我个人以为这句话真正想表达的意义应该是:
WeakMaps hold “weak” references to key objects,
翻译过来应该是 WeakMaps 坚持了对键名所援用的对象的弱援用。
我们先聊聊弱援用:
在盘算机程序设计中,弱援用与强援用相对,是指不能确保其援用的对象不会被渣滓接纳器接纳的援用。 一个对象若只被弱援用所援用,则被认为是不可接见(或弱可接见)的,并因而能够在任什么时候刻被接纳。
在 JavaScript 中,平常我们竖立一个对象,都是竖立一个强援用:
var obj = new Object();
只需当我们手动设置 obj = null
的时刻,才有能够接纳 obj 所援用的对象。
而假如我们能竖立一个弱援用的对象:
// 假定能够如许竖立一个
var obj = new WeakObject();
我们什么都不必做,只用静静的守候渣滓接纳机制实行,obj 所援用的对象就会被接纳。
我们再来看看这句:
WeakMaps 坚持了对键名所援用的对象的弱援用
一般状况下,我们举个例子:
const key = new Array(5 * 1024 * 1024);
const arr = [
[key, 1]
];
运用这类体式格局,我们实在竖立了 arr 对 key 所援用的对象(我们假定这个真正的对象叫 Obj)的强援用。
所以当你设置 key = null
时,只是去掉了 key 对 Obj 的强援用,并没有去除 arr 对 Obj 的强援用,所以 Obj 照样不会被接纳掉。
Map 范例也是相似:
let map = new Map();
let key = new Array(5 * 1024 * 1024);
// 竖立了 map 对 key 所援用对象的强援用
map.set(key, 1);
// key = null 不会致使 key 的原援用对象被接纳
key = null;
我们能够经由过程 Node 来证实一下这个题目:
// 许可手动实行渣滓接纳机制
node --expose-gc
global.gc();
// 返回 Nodejs 的内存占用状况,单元是 bytes
process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4M
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46751472 注重这里大约是 44.6M
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M
// 这句话实际上是无用的,由于 key 已经是 null 了
map.delete(key);
global.gc();
process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M
假如你想要让 Obj 被接纳掉,你须要先 delete(key)
然后再 key = null
:
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
map.delete(key);
key = null;
我们依旧经由过程 Node 证实一下:
node --expose-gc
global.gc();
process.memoryUsage(); // heapUsed: 4638376 ≈ 4.4M
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46727816 ≈ 44.6M
map.delete(key);
global.gc();
process.memoryUsage(); // heapUsed: 46748352 ≈ 44.6M
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 4808064 ≈ 4.6M
这个时刻就要说到 WeakMap 了:
const wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
key = null;
当我们设置 wm.set(key, 1)
时,实在竖立了 wm 对 key 所援用的对象的弱援用,但由于 let key = new Array(5 * 1024 * 1024)
竖立了 key 对所援用对象的强援用,被援用的对象并不会被接纳,然则当我们设置 key = null
的时刻,就只需 wm 对所援用对象的弱援用,下次渣滓接纳机制实行的时刻,该援用对象就会被接纳掉。
我们用 Node 证实一下:
node --expose-gc
global.gc();
process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4M
const wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M
所以 WeakMap 能够帮你免却手动删除对象关联数据的步骤,所以当你不能或许不想掌握关联数据的生命周期时就能够斟酌运用 WeakMap。
总结这个弱援用的特征,就是 WeakMaps 坚持了对键名所援用的对象的弱援用,即渣滓接纳机制不将该援用斟酌在内。只需所援用的对象的其他援用都被消灭,渣滓接纳机制就会开释该对象所占用的内存。也就是说,一旦不再须要,WeakMap 内里的键名对象和所对应的键值对会自动消逝,不必手动删除援用。
也恰是由于如许的特征,WeakMap 内部有多少个成员,取决于渣滓接纳机制有无运转,运转前后极能够成员个数是不一样的,而渣滓接纳机制什么时候运转是不可展望的,因而 ES6 划定 WeakMap 不可遍历。
所以 WeakMap 不像 Map,一是没有遍历操纵(即没有keys()、values()和entries()要领),也没有 size 属性,也不支持 clear 要领,所以 WeakMap只需四个要领可用:get()、set()、has()、delete()。
运用
1. 在 DOM 对象上保留相干数据
传统运用 jQuery 的时刻,我们会经由过程 $.data() 要领在 DOM 对象上贮存相干信息(就比如在删除按钮元素上贮存帖子的 ID 信息),jQuery 内部会运用一个对象治理 DOM 和对应的数据,当你将 DOM 元素删除,DOM 对象置为空的时刻,相干联的数据并不会被删除,你必需手动实行 $.removeData() 要领才删撤除相干联的数据,WeakMap 就能够简化这一操纵:
let wm = new WeakMap(), element = document.querySelector(".element");
wm.set(element, "data");
let value = wm.get(elemet);
console.log(value); // data
element.parentNode.removeChild(element);
element = null;
2. 数据缓存
从上一个例子,我们也能够看出,当我们须要关联对象和数据,比如在不修正原有对象的状况下贮存某些属性或许依据对象贮存一些盘算的值等,而又不想治理这些数据的死活时异常合适斟酌运用 WeakMap。数据缓存就是一个异常好的例子:
const cache = new WeakMap();
function countOwnKeys(obj) {
if (cache.has(obj)) {
console.log('Cached');
return cache.get(obj);
} else {
console.log('Computed');
const count = Object.keys(obj).length;
cache.set(obj, count);
return count;
}
}
3. 私有属性
WeakMap 也能够被用于完成私有变量,不过在 ES6 中完成私有变量的体式格局有很多种,这只是个中一种:
const privateData = new WeakMap();
class Person {
constructor(name, age) {
privateData.set(this, { name: name, age: age });
}
getName() {
return privateData.get(this).name;
}
getAge() {
return privateData.get(this).age;
}
}
export default Person;
ES6 系列
ES6 系列目次地点:https://github.com/mqyqingfeng/Blog
ES6 系列估计写二十篇摆布,旨在加深 ES6 部份知识点的明白,重点解说块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模仿完成、模块加载计划、异步处置惩罚等内容。
假如有毛病或许不严谨的处所,请务必赋予斧正,非常谢谢。假如喜好或许有所启示,迎接 star,对作者也是一种勉励。