5

JS WeakMap应该什么时候使用

 3 years ago
source link: https://www.zhangxinxu.com/wordpress/2021/08/js-weakmap-es6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10064 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

红梅葡萄占位图

一、从哪里开始呢?

算了,还是先说结论吧。

二、WeakMap什么时候用?

首先,大家要明白,就算没有 WeakMap,JS的世界也是照常运转,WeakMap在JS世界的地位,就和其名称一样——“weak”,弱。

属于那种有作用,但是并不在关键位置干大事的人,有点类似于一些CSS功能选择器,虽然使用更简单更方便,但是平常使用JS实现的交互效果挺好的,所以并不那么流行与普及。

WeakMap的作用就是可以更有效的垃圾回收、释放内存。

我们平常开发的Web应用都是如此的简单,就算代码很垃圾,吃了很多内存那又如何,页面照样流畅,用户照样无感知。

所以,对于绝大多数开发者和应用程序而言,WeakMap的商业价值是很低的。

这么一说,似乎WeakMap没什么好学的。

其实不然。

如果是超大型应用,或者用户基数庞大的产品,或者是服务器这种负载较高的场景,对于内存管理要求就很高,此时WeakMap的优势就可以体现。

这其实有点悖论的味道在里面。

上述所有需要用到WeakMap的场景都一定是需要资深前端开发参与的场景,而如果你连WeakMap都不知道,就谈不上资深,挑上面的大梁可能会闪着腰。

也就是,你学会了WeakMap以及类似的JS知识点,才有机会参与必须要使用这些JS特性的项目中,完成自我价值的证明与贡献。

所以,没有什么知识是没用的,只是时机的问题,所谓厚积薄发,就是这样一个意思。

垃圾?内存?

就内存管理而言,JS开发人员可以归为下面几种:

  1. 内存?什么内存?我JS想怎么写就怎么写,运行不挺好的!反正浏览器页面关掉什么都没了。
  2. 恩,这里这个对象之后没用了,我可以设置为null。意识是好的,设置也设置了,就是内存究竟有没有释放不得而知,看运气。
  3. 这里设置null不行,因为对象在其他地方也引用了,其他引用的地方也要删除,属于理解比较深刻的,JS基本功很扎实的。

举个大家比较容易懂的例子,已知页面中有个DOM元素,HTML结构如下:

<img id="img" src="https://bookcover.yuewen.com/qdbimg/349573/1027030348/180">

然后,需要删除此DOM元素,小明是这么处理的:

var eleImage = document.getElementById('img');
eleImage.remove();

这个处理有没有什么问题?

从效果上来看,完全解决了需求。

但是实际上,虽然页面中的图片元素删除了,但是内存中的这个DOM对象依然存在的。

我们不妨这样测试下:

var eleImage = document.getElementById('img');
eleImage.remove();

setTimeout(() => {
  document.body.append(eleImage);
}, 2000);

就会看到图片被删除了(如下GIF录屏),然后过了2秒钟又出现在了页面上,因为eleImage还在内存中,并未清除,不会被回收。

删除后2秒又出现

所以,如果确定eleImage不再需要,需要多执行一句,同时设为 null。

var eleImage = document.getElementById('img');
eleImage.remove();
eleImage = null;

这样,JS在执行垃圾回收的时候,就会把eleImage这个垃圾收回,释放内存。

大家可以看看自己是不关心内存的那类JS开发,还是会注意释放不需要的内存的JS开发。

OK,事情还没完,有时候,设置eleImage为null,并不能真正回收内存。

例如实际开发,有时候需要记住初始的outerHTML字符,方便还原。为了和原始DOM产生关联,有些开发就把DOM元素和outerHTML字符串放在同一个数组中进行管理:

var eleImage = document.getElementById('img');
var storage = {
    arrDom: [eleImage, eleImage.outerHTML]
};
eleImage.remove();
eleImage = null;

此时,eleImage这个DOM对象其实还在内存中。因为 eleImage 被 storage.arrDom 引用了,即使eleImage设为null也无法将内存彻底释放。

var eleImage = document.getElementById('img');
var storage = {
    arrDom: [eleImage, eleImage.outerHTML]
};
eleImage.remove();
eleImage = null;

setTimeout(() => {
  document.body.append(storage.arrDom[0]);
}, 2000);

同样可以看到,图片被删除后,2秒后又出现了。

这个还是看实时效果吧。

点击后面的按钮查看效果:

180

此时,要想完全把图像的内存释放,还需要执行下面这行:

storage.arrDom[0] = null;

大家试想下,如果是你,可以释放内存做到这一步吗?如果可以做到,那你就可以归类为资深的JS前端开发那一类了。

但是,纯靠技术手段,人工识别哪些地方的内存要释放,实在是太累了。就算懂行的人有时候嫌麻烦,都懒得去管理,那有没有什么手段,我只要变量设为null,所有有引用的地方的内存都自动释放呢?

这就可以考虑使用WeakMap了。

上面的例子,如果使用WeakMap实现回是怎样的呢?

代码说话:

var eleImage = document.getElementById('img');
var storeMap = new WeakMap();
storeMap.set(eleImage, eleImage.outerHTML);
eleImage.remove();
eleImage = null;

同样是缓存图片的outerHTML数据,但是这里使用了 WeakMap 对象,将 eleImage 作为一个弱键,这样,eleImage一旦设置为 null,所有相关的数据都会被释放。

究竟内存释放没有,上面的例子不好测试,一是要借助工具,二是内存变化很小,看不出来。

不过,我们可以使用Map对象对比下,如果是Map对象,eleImage作为键,那就是强引用,是一直在内存中的,例如:

var eleImage = document.getElementById('img');
var storeMap = new Map();
storeMap.set(eleImage, eleImage.outerHTML);
eleImage.remove();
eleImage = null;

setTimeout(() => {
  document.body.append(storeMap.keys().next().value);
}, 2000);

可以看到,虽然eleImage remove掉了,还设为了null,但是依然在内存中,可以append到页面中。

根据上面的分析和描述,我们可以得出下面的结论。

当我们需要在某个对象上临时存放数据的时候,请使用WeakMap,尤其对于JS理解不是很深刻的开发人员,更是如此,因为省心,不要关心经常挂在嘴边的“内存泄露”问题。

因为到时候只需要删除该对象,所有相关的引用和关联的内存都会被释放。

也就是,虽然我看不懂代码是怎么执行的,但是我这么写的,性能就是好,逼格就是高!

看不懂

三、回到WeakMap语法本身

说了这么多,是时候介绍 WeakMap 语法的真身了。

let myWm = new WeakMap()

此时,myWm就是一个新的WeakMap对象,包括了下面这些方法:

// 删除键
myWm.delete(key);
// 设置键和值
myWm.set(key, value);
// 是否包含某键
myWm.has(key);
// 获取键对应的值
myWm.get(key);
  1. key只能是对象,不能是原始数据类型(字符串、数字、true或false,null,undefined,symbol等类型)
  2. WeakMap中的键是无法枚举的

key只能是对象

下面的用法都是可以的:

myWm.set([], 1);
myWm.set(new Date(), '鑫空间');
myWm.set(()=>{}, 1);
myWm.set(document.createElement('by-zhangxinxu'), 1);

但是如果key不是对象,而是字符串之类的基本类型,就会报错,例如:

// 会报错
myWm.set('css新世界', true);

此时会报下面的错误:

“TypeError: Invalid value used as weak map key

因此WeakMap适合用在在对象上临时缓存数据的场景。

key无法枚举

不同于Map对象,Map对象是可以枚举的,有keys()values()entries()方法,还可以使用forEach遍历。

但是WeakMap无法枚举,WeakMap的这个特性也可以用来模拟私有属性。

const myWm = new WeakMap();
class Fish {
    constructor(name) {
        myWm.set(this, { 
            _fishbone: ['草鱼', '鲫鱼', '青鱼', '鲤鱼', '鲢鱼', '鳙鱼', '鳊鱼', '翘嘴', '餐条'],
        });
        this.name = name;
    }

    isBone() {
        return myWm.get(this)._fishbone.includes(this.name);
    }
}

// 测试,买了两条鱼
let fish1 = new Fish('草鱼');
let fish2 = new Fish('回鱼');

// 返回 true,有刺
console.log(fish1.isBone());
// 返回 false,没有肌间刺
console.log(fish2.isBone());

上面的代码中,_fishbone虽然和Fish对象相关联,但是却无法通过Fish对象直接获取。

如果不知道名称,也无法通过myWm遍历出来。

关于WeakMap更详细的语法介绍和示意可参考MDN文档:MDN WeakMap

四、结束语

考考大家,我昨天钓的下面3种鱼,哪种鱼有肌间刺,哪几种鱼没有肌间刺?

钓货

好,本文内容就上面这些。

如果文中有表述不准确的地方,或者有所遗漏,欢迎指正。

如果您觉得文章不错,也欢迎分享。

2764.svg

(本篇完)1f44d.svg 是不是学到了很多?可以分享到微信
1f44a.svg 有话要说?点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK