ES6 系列之 WeakMap的使用示例


Posted in Javascript onAugust 06, 2018

前言

我们先从 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;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Firefox+FireBug使JQuery的学习更加轻松愉快
Jan 01 Javascript
js调用iframe实现打印页面内容的方法
Mar 04 Javascript
JavaScript中获取高度和宽度函数总结
Oct 08 Javascript
JavaScript字符串对象
Jan 14 Javascript
vue2组件实现懒加载浅析
Mar 29 Javascript
写给小白看的JavaScript异步
Nov 29 Javascript
layui中使用jquery控制radio选中事件的示例代码
Aug 15 jQuery
vue2.0 使用element-ui里的upload组件实现图片预览效果方法
Sep 04 Javascript
vue给对象动态添加属性和值的实例
Sep 09 Javascript
layui导出所有数据的例子
Sep 10 Javascript
js验证密码强度解析
Mar 18 Javascript
node.js如何自定义实现一个EventEmitter
Jul 16 Javascript
JavaScript选择排序算法原理与实现方法示例
Aug 06 #Javascript
ES6 中可以提升幸福度的小功能
Aug 06 #Javascript
原生JS实现的轮播图功能详解
Aug 06 #Javascript
在 Angular6 中使用 HTTP 请求服务端数据的步骤详解
Aug 06 #Javascript
animate.css在vue项目中的使用教程
Aug 05 #Javascript
iconfont的三种使用方式详解
Aug 05 #Javascript
vue-content-loader内容加载器的使用方法
Aug 05 #Javascript
You might like
php调整服务器时间的方法
2015/04/03 PHP
PHP实现redis限制单ip、单用户的访问次数功能示例
2018/06/16 PHP
TP框架实现上传一张图片和批量上传图片的方法分析
2020/04/23 PHP
php模拟post提交请求调用接口示例解析
2020/08/07 PHP
一个简单的实现下拉框多选的插件可移植性比较好
2014/05/05 Javascript
js实现时间显示几天前、几小时前或者几分钟前的方法集锦
2015/05/29 Javascript
JavaScript数据存储 Cookie篇
2016/07/02 Javascript
JavaScript跨域调用基于JSON的RESTful API
2016/07/09 Javascript
JS多文件上传的实例代码
2017/01/11 Javascript
Vuejs 用$emit与$on来进行兄弟组件之间的数据传输通信
2017/02/23 Javascript
jQuery EasyUI结合zTree树形结构制作web页面
2017/09/01 jQuery
判断滚动条滑到底部触发事件(实例讲解)
2017/11/15 Javascript
jQuery 点击获取验证码按钮及倒计时功能
2018/09/20 jQuery
浅谈Angular单元测试总结
2019/03/22 Javascript
微信小程序实现时间进度条功能
2020/11/17 Javascript
JS实现旋转木马轮播图
2020/01/01 Javascript
使用scrapy实现爬网站例子和实现网络爬虫(蜘蛛)的步骤
2014/01/23 Python
python搜索指定目录的方法
2015/04/29 Python
python采用django框架实现支付宝即时到帐接口
2016/05/17 Python
利用python Selenium实现自动登陆京东签到领金币功能
2019/10/31 Python
Python IDLE或shell中切换路径的操作
2020/03/09 Python
python应用Axes3D绘图(批量梯度下降算法)
2020/03/25 Python
python连接mysql数据库并读取数据的实现
2020/09/25 Python
HTML5语义化元素你真的用对了吗
2019/08/22 HTML / CSS
快时尚眼镜品牌,全国连锁眼镜店:LOHO眼镜生活
2018/10/08 全球购物
定制别致的瑜伽垫:Sugarmat
2019/06/21 全球购物
美国乒乓球设备、配件和服装品牌:Killerspin
2020/06/07 全球购物
大学生村官工作总结2015
2015/04/09 职场文书
食品药品安全责任书
2015/05/11 职场文书
交通事故起诉书
2015/05/19 职场文书
2015毕业设计工作总结
2015/07/24 职场文书
担保书范文
2019/07/09 职场文书
python3美化表格数据输出结果的实现代码
2021/04/14 Python
mysql对于模糊查询like的一些汇总
2021/05/09 MySQL
Python调用腾讯API实现人脸身份证比对功能
2022/04/04 Python
Windows和Linux上部署Golang并运行程序
2022/04/22 Servers