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 相关文章推荐
JS动画效果代码3
Apr 03 Javascript
JQuery autocomplete 使用手册
Apr 01 Javascript
javascript一元操作符(递增、递减)使用示例
Aug 07 Javascript
js分页工具实例
Jan 28 Javascript
基于Bootstrap重置输入框内容按钮插件
May 12 Javascript
JavaScript结合Bootstrap仿微信后台多图文界面管理
Jul 22 Javascript
Javascript实现找不同色块的游戏
Jul 17 Javascript
浅谈vue 单文件探索
Sep 05 Javascript
vue通信方式EventBus的实现代码详解
Jun 10 Javascript
小程序的上传文件接口的注意要点解析
Sep 17 Javascript
微信浏览器下拉黑边解决方案 wScroollFix
Jan 21 Javascript
js实现拾色器插件(ColorPicker)
May 21 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和ACCESS写聊天室(九)
2006/10/09 PHP
php5.3提示Function ereg() is deprecated Error问题解决方法
2014/11/12 PHP
thinkphp区间查询、统计查询与SQL直接查询实例分析
2014/11/24 PHP
PHP新特性之字节码缓存和内置服务器
2017/08/11 PHP
javascript跨域方法、原理以及出现问题解决方法(详解)
2015/08/06 Javascript
ECMA5数组的新增方法有哪些及forEach()模仿实现
2015/11/03 Javascript
nodejs 实现钉钉ISV接入的加密解密方法
2017/01/16 NodeJs
基于webpack 实用配置方法总结
2017/09/28 Javascript
浅谈Vue网络请求之interceptors实际应用
2018/02/28 Javascript
实例详解vue中的$root和$parent
2019/04/29 Javascript
layui table 列宽百分比显示的实现方法
2019/09/28 Javascript
js事件机制----捕获与冒泡机制实例分析
2020/05/22 Javascript
在Echarts图中给坐标轴加一个标识线markLine
2020/07/20 Javascript
jQuery实现简单全选框
2020/09/13 jQuery
python实现从一组颜色中找出与给定颜色最接近颜色的方法
2015/03/19 Python
Python实现购物车程序
2018/04/16 Python
Python 3.7新功能之dataclass装饰器详解
2018/04/21 Python
python开启摄像头以及深度学习实现目标检测方法
2018/08/03 Python
python判断列表的连续数字范围并分块的方法
2018/11/16 Python
Python如何使用字符打印照片
2020/01/03 Python
Python Handler处理器和自定义Opener原理详解
2020/03/05 Python
Python Merge函数原理及用法解析
2020/09/16 Python
python爬取招聘要求等信息实例
2020/11/20 Python
html5中JavaScript removeChild 删除所有节点
2014/05/16 HTML / CSS
Nuts.com:优质散装,批发坚果、干果和巧克力等
2017/03/21 全球购物
澳大利亚最大的女装零售商:Millers
2017/09/10 全球购物
俄罗斯鲜花递送:AMF
2020/04/24 全球购物
《金钱的魔力》教学反思
2014/02/24 职场文书
学校招生宣传广告词
2014/03/19 职场文书
英文推荐信格式范文
2014/05/09 职场文书
“四风”问题整改措施和努力方向
2014/09/20 职场文书
车辆挂靠协议书
2016/03/23 职场文书
Java框架入门之简单介绍SpringBoot框架
2021/06/18 Java/Android
go使用Gin框架利用阿里云实现短信验证码功能
2021/08/04 Golang
详解Python+OpenCV进行基础的图像操作
2022/02/15 Python
mysql中关键词exists的用法实例详解
2022/06/10 MySQL