学习时间:2020.05.26
学习章节:《你不知道的 WeakMap》javascript
原文主要复习了“JavaScript垃圾回收机制”,“Map/WeakMap区别”和“WeakMap 属性和方法”。这很好弥补被我忽视的知识点。
另外,咱们能够经过原文,以相同方式再去学 Set/WeakSet,效果会更好,本文后面也会介绍到。
总结开始,先看原文大纲:
html
在开始介绍 WeakMap 以前,先复习一遍 JavaScript 中垃圾回收机制,这跟后面的 WeakMap/WeakSet 关系较大。
java
垃圾回收(Garbage Collection,缩写为GC))是一种自动的存储器管理机制。当某个程序占用的一部份内存空间再也不被这个程序访问时,这个程序会借助垃圾回收算法向操做系统归还这部份内存空间。垃圾回收器能够减轻程序员的负担,也减小程序中的错误。垃圾回收最先起源于LISP语言。
目前许多语言如Smalltalk、Java、C#和D语言都支持垃圾回收器,咱们熟知的 JavaScript 具备自动垃圾回收机制。
在 JavaScript 中,原始类型的数据被分配到栈空间中,引用类型的数据会被分配到堆空间中。
**node
当函数 showName
调用完成后,经过下移 ESP(Extended Stack Pointer)指针,来销毁 showName
函数,以后调用其余函数时,将覆盖掉旧内存,存放另外一个函数的执行上下文,实现垃圾回收。
图片来自《浏览器工做原理与实践》
程序员
堆中数据垃圾回收策略的基础是:代际假说(The Generational Hypothesis)。即:算法
这两个特色不只仅适用于 JavaScript,一样适用于大多数的动态语言,如 Java、Python 等。
V8 引擎将堆空间分为新生代(存放生存时间短的对象)和老生代(存放生存时间长的对象)两个区域,并使用不一样的垃圾回收器。
数组
不论是哪一种垃圾回收器,都使用相同垃圾回收流程:标记活动对象和非活动对象,回收非活动对象的内存,最后内存整理。
**浏览器
使用 Scavenge 算法处理,将新生代空间对半分为两个区域,一个对象区域,一个空闲区域。
图片来自《浏览器工做原理与实践》
执行流程:缓存
固然,这也存在一些问题:若复制操做的数据较大则影响清理效率。
JavaScript 引擎的解决方式是:将新生代区域设置得比较小,并采用对象晋升策略(通过两次回收仍存活的对象,会被移动到老生区),避免由于新生代区域较小引发存活对象装满整个区域的问题。
函数
分为:标记 - 清除(Mark-Sweep)算法,和标记 - 整理(Mark-Compact)算法。
a)标记 - 清除(Mark-Sweep)算法
过程:
图片来自《浏览器工做原理与实践》
b)标记 - 整理(Mark-Compact)算法
过程:
图片来自《浏览器工做原理与实践》
WeakMap
结构与 Map
结构相似,也是用于生成键值对的集合。
区别:
Map
对象的键能够是任何类型,但 WeakMap
对象中的键只能是对象引用( null
除外);const map = new WeakMap(); map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key map.set(null, 2) // TypeError: Invalid value used as weak map key
WeakMap
不能包含无引用的对象,不然会被自动清除出集合(垃圾回收机制);WeakMap
对象没有 size
属性,是不可枚举的,没法获取集合的大小。const map = new WeakMap(); const user1 = {name: 'leo'}; const user2 = {name: 'pingan'}; map.set(user1, 'good~'); map.set(user2, 'hello'); map.map(item => console.log(item)) //Uncaught TypeError: map.map is not a function
1.赋值和搜索操做都是 O(n) 的时间复杂度,由于这两个操做都须要遍历所有整个数组来进行匹配。
2.可能会致使内存泄漏,由于数组会一直引用着每一个键和值。
相比之下, WeakMap
持有的是每一个键对象的 “弱引用”,这意味着在没有其余引用存在时垃圾回收能正确进行。 原生 WeakMap
的结构是特殊且有效的,其用于映射的 key 只有在其没有被回收时才是有效的。
当数据量越大,则垃圾回收效果越明显。
经过命令行执行 node --expose-gc weakmap.js
查看对比效果。
其中 --expose-gc
参数表示容许手动执行垃圾回收机制。
// weakmap.js const objNum = 10 * 1024 * 1024; const useType = 1; // 修改 useType 值来测试Map和WeakMap const curType = useType == 1 ?"【Map】" : "【WeakMap】"; let arr = new Array(objNum); function usageSize() { const used = process.memoryUsage().heapUsed; return Math.round((used / 1024 / 1024) * 100) / 100 + "M"; } if (useType == 1) { global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); const map = new Map(); map.set(arr, 1); global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); arr = null; global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); console.log("=====") } else { global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); const map = new WeakMap(); global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); arr = null; global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); console.log("=====") }
WeakMap
对象是一组键/值对的集合,其中的键是 弱引用 的。
WeakMap 的 key 只能是 Object 类型。
原始数据类型是不能做为 key 的(好比 Symbol)。WeakMap
只有四个方法可用:get()
、set()
、has()
、delete()
。
**
具体属性和方法介绍,可查看 《MDN WeakMap》。
原文中介绍了“经过 WeakMap 缓存计算结果”和“在 WeakMap 中保留私有数据”两种应用场景。
另外还有一种比较常见的场景:以 DOM节点做为键名的场景。
场景1:当咱们想要为DOM添加数据时,可以使用 WeakMap
。
好处在于,当DOM元素移除时,对应 WeakMap 记录也会自动移除:
<div id="WeakMap"></div>
const wm = new WeakMap(); const weakMap = document.getElementById('WeakMap'); wm.set(weakMap, 'some information'); wm.get(weakMap) //"some information"
场景2:当咱们想要为DOM元素添加事件监听时,可以使用 WeakMap
。
<button id="button1">按钮1</button> <button id="button2">按钮2</button>
const button1 = document.getElementById('button1'); const button2 = document.getElementById('button2'); const handler1 = () => { console.log("button1 被点击") }; const handler2 = () => { console.log("button2 被点击") }; // 代码1 button1.addEventListener('click', handler1, false); button2.addEventListener('click', handler2, false); // 代码2 const listener = new WeakMap(); listener.set(button1, handler1); listener.set(button2, handler2); button1.addEventListener('click', listener.get(button1), false); button2.addEventListener('click', listener.get(button2), false);
代码2比起代码1的好处是:因为监听函数是放在 WeakMap 里面,
则一旦 DOM 对象button1 / button2消失,与它绑定的监听函数handler1和handler2 也会自动消失。
WeakSet
结构与 Set
相似,也是不重复的值的集合。
区别:
WeakSet
的成员只能是对象,而不能是其余类型的值;const ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set ws.add(Symbol()) // TypeError: invalid value used in weak set
WeakSet
中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet
对该对象的引用;WeakSet
对象没有 size
属性,是不可枚举的,没法获取集合的大小。经过命令行执行 node --expose-gc weakset.js
查看对比效果。
// weakset.js const objNum = 5000 * 1024; const useType = 1; const curType = useType == 1 ?"【Set】" : "【WeakSet】"; let obj = []; for (let k = 0; k < objNum; k++) { obj[k] = {} } function usageSize() { const used = process.memoryUsage().heapUsed; return Math.round((used / 1024 / 1024) * 100) / 100 + "M"; } if (useType == 1) { global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); const sets = new Set([...obj]); global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); obj = null; global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); console.log("=====") } else { global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); const sets = new WeakSet(obj); global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); obj = null; global.gc(); console.log(objNum + '个' + curType + '占用内存:' + usageSize()); console.log("=====") }
本文首先复习了《你不知道的 WeakMap》中核心知识点,从新回顾了“垃圾回收机制”,“Map VS WeakMap”和“WeakMap 介绍和应用”,最后延伸复习了“Set/WeakSet”相关知识点。在实际业务开发中,最好也能考虑垃圾回收机制的合理使用,这也是提高产品性能的一个很是经常使用的方式。