摘要:一个对象若只被弱引用所引用,则被认为是不可访问或弱可访问的,并因此可能在任何时刻被回收。也就是说,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。
前言
我们先从 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 key2. 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,对作者也是一种鼓励。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/96299.html
摘要:前言在阅读入门的时候,零散的看到有私有变量的实现,所以在此总结一篇。构造函数应该只做对象初始化的事情,现在为了实现私有变量,必须包含部分方法的实现,代码组织上略不清晰。 前言 在阅读 《ECMAScript 6 入门》的时候,零散的看到有私有变量的实现,所以在此总结一篇。 1. 约定 实现 class Example { constructor() { this...
摘要:返回一个布尔值,表示该值是否为的成员。清除所有成员,没有返回值。返回的都是遍历器对象。结构的实例的方法,用于对每个成员执行某种操作,没有返回值。这个特点意味着,无法引用的成员,因此是不可遍历的。数组成员是一个或多个表示键值对的一维数组。 本文字数:4700+,阅读时间约10分钟。 如果有理解不到位的地方,欢迎大家纠错。 一、Set 【01】Set是一种数据结构。类似于数组,但是成员的值...
摘要:事实上,不仅仅是数组,任何具有接口且每个成员都是一个双元素的数组的数据结构都可以当做构造函数的参数。返回所有成员的遍历器需要特别注意的是,的遍历顺序就是插入顺序。转为转为,正常情况下,所有键名都是字符串。 Map JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提...
摘要:今天这篇文章我们来看看一道必会面试题,即如何实现一个深拷贝。木易杨注意这里使用上面测试用例测试一下一个简单的深拷贝就完成了,但是这个实现还存在很多问题。 引言 上篇文章详细介绍了浅拷贝 Object.assign,并对其进行了模拟实现,在实现的过程中,介绍了很多基础知识。今天这篇文章我们来看看一道必会面试题,即如何实现一个深拷贝。本文会详细介绍对象、数组、循环引用、引用丢失、Symbo...
Set有对应的WeakSet, Map也有WeakMap。这一篇,我们就来学习一下WeakMap有写什么特性。先来看一下WeakMap的基本特性: 1: WeakMap是一种存储多个键值对的无序列表 2: WeakMap的键必须是非null的对象类型 3: WeakMap的键对应的值,可以是任意类型 接下来看一下WeakMap的接口方法:一:WeakMap的新建与初始化与Map相同的,WeakM...
阅读 1035·2023-04-25 17:51
阅读 2851·2021-11-23 09:51
阅读 1469·2021-11-08 13:21
阅读 2428·2021-09-22 15:14
阅读 1514·2019-08-30 12:48
阅读 1076·2019-08-29 12:44
阅读 1137·2019-08-26 12:21
阅读 1396·2019-08-26 10:47