这篇文章发布于 2021年08月15日,星期日,12:40,归类于 JS API。 阅读 22708 次, 今日 4 次 10 条评论
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开发人员可以归为下面几种:
- 内存?什么内存?我JS想怎么写就怎么写,运行不挺好的!反正浏览器页面关掉什么都没了。
- 恩,这里这个对象之后没用了,我可以设置为null。意识是好的,设置也设置了,就是内存究竟有没有释放不得而知,看运气。
- 这里设置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还在内存中,并未清除,不会被回收。
所以,如果确定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秒后又出现了。
这个还是看实时效果吧。
点击后面的按钮查看效果:
此时,要想完全把图像的内存释放,还需要执行下面这行:
storage.arrDom[0] = null;
大家试想下,如果是你,可以释放内存做到这一步吗?如果可以做到,那你就可以归类为资深的JS前端开发那一类了。
但是,纯靠技术手段,人工识别哪些地方的内存要释放,实在是太累了。就算懂行的人有时候嫌麻烦,都懒得去管理,那有没有什么手段,我只要变量设为null,所有有引用的地方的内存都自动释放呢?
这就可以考虑使用WeakMap了。
//zxx: 如果你看到这段文字,说明你现在访问是体验糟糕的垃圾盗版网站,你可以访问原文获得很好的体验:https://www.zhangxinxu.com/wordpress/?p=10064(作者张鑫旭)
使用场景
上面的例子,如果使用WeakMap实现回是怎样的呢?
代码说话:
var eleImage = document.getElementById('img'); var storeMap = new WeakMap(); storeMap.set(eleImage, eleImage.outerHTML); eleImage.remove(); eleImage = null;
同样是缓存图片的outerHTML数据,但是这里使用了 WeakMap 对象,将 eleImage 作为一个弱键,这样,eleImage一旦设置为 null,所有相关的数据都会被释放。
究竟内存释放没有,上面的例子不好测试,一是要借助工具,二是内存变化很小,看不出来。
更新于当日
经过评论反馈,FinalizationRegistry是可以检测垃圾回收的执行,并触发相应的回调的。
不过,我们可以使用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);
说明
- key只能是对象,不能是原始数据类型(字符串、数字、true或false,null,undefined,symbol等类型)
- 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种鱼,哪种鱼有肌间刺,哪几种鱼没有肌间刺?
好,本文内容就上面这些。
如果文中有表述不准确的地方,或者有所遗漏,欢迎指正。
如果您觉得文章不错,也欢迎分享。
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=10064
(本篇完)
- 实用的JS对象分组静态方法Object.groupBy() (0.900)
- ES5中新增的Array方法详细说明 (0.300)
- HTML <area><map>标签及在实际开发中的应用 (0.300)
- ES6 JavaScript Promise的感性认知 (0.100)
- HTMLUnknownElement与HTML5自定义元素的故事 (0.100)
- 简单了解ES6/ES2015 Symbol() 方法 (0.100)
- JS字符串补全方法padStart()和padEnd()简介 (0.100)
- 万岁,浏览器原生支持ES6 export和import模块啦! (0.100)
- 看,for..in和for..of在那里吵架! (0.100)
- ES6 Math方法和Number新特性简介 (0.100)
- Proxy是代理,Reflect是干嘛用的? (RANDOM - 0.100)
nice!
你好骚啊
说实话用 WeakMap 来存 Element -> string 这种还是比较牵强的,毕竟你都拿到对象了,直接设置成属性不是也能达到一样的效果,怕名字撞了也可以用 Symbol
我能想到的一个用法是在没有 private 属性的时候模拟 private 的效果(当然,这里说的是运行时强制性 private ,不是typescript的那种编译期检查)
把想要加的私有属性放到 WeakMap 里(a.#f = x 变成 fmap.set(a, x)),那就只有能访问到那个 WeakMap 的对象能拿到“私有属性”的值,外部无法通过任何手段观测,即使使用 DevTools 也很难观察出这种“私有属性”,必须仔细检查源码才可以发现,相比于一般的 Map ,用 WeakMap 的好处自然就是它不需要关心原对象的内存释放问题
缝缝补补又三年,JS这东西破得很
只要 eleImage 所在的作用域结束, 变量不可达, 也是能正常回收的。
顺便自定义大法好, 不 block js 也阔以 233
just block js file can block your new ad….lol
厉害了,学习了!
才在京东买的新世界,就发现了个adblock专享推荐,还是签名版的,哈哈哈哈哈
WeakRef, FinalizationRegistry
厉害了,学习了!