垃圾回收机制
咱们晓得,程序运行中会有一些垃圾数据不再应用,须要及时开释进来,如果咱们没有及时开释,这就是内存泄露
JS 中的垃圾数据都是由垃圾回收(Garbage Collection,缩写为 GC)器主动回收的,不须要手动开释,它是如何做的喃?
很简略,JS 引擎中有一个后盾过程称为垃圾回收器,它监督所有对象,察看对象是否可被拜访,而后依照固定的工夫距离周期性的删除掉那些不可拜访的对象即可
当初各大浏览器通常用采纳的垃圾回收有两种办法:
- 援用计数
- 标记革除
援用计数
最早最简略的垃圾回收机制,就是给一个占用物理空间的对象附加一个援用计数器,当有其它对象援用这个对象时,这个对象的援用计数加一,反之解除时就减一,当该对象援用计数为 0 时就会被回收。
该形式很简略,但会引起内存透露:
// 循环援用的问题
function temp(){var a={};
var b={};
a.o = b;
b.o = a;
}
这种状况下每次调用 temp
函数,a
和 b
的援用计数都是 2
,会使这部分内存永远不会被开释,即内存透露。当初曾经很少应用了,只有低版本的 IE 应用这种形式。
标记革除
V8 中主垃圾回收器就采纳标记革除法进行垃圾回收。次要流程如下:
- 标记:遍历调用栈,看老生代区域堆中的对象是否被援用,被援用的对象标记为流动对象,没有被援用的对象(待清理)标记为垃圾数据。
- 垃圾清理:将所有垃圾数据清理掉
(图片起源:How JavaScript works: memory management + how to handle 4 common memory leaks)
在咱们的开发过程中,如果咱们想要让垃圾回收器回收某一对象,就将对象的援用间接设置为 null
var a = {}; // {} 可拜访,a 是其援用
a = null; // 援用设置为 null
// {} 将会被从内存里清理进来
但如果一个对象被屡次援用时,例如作为另一对象的键、值或子元素时,将该对象援用设置为 null
时,该对象是不会被回收的,仍然存在
var a = {};
var arr = [a];
a = null;
console.log(arr)
// [{}]
如果作为 Map
的键喃?
var a = {};
var map = new Map();
map.set(a, '三分钟学前端')
a = null;
console.log(map.keys()) // MapIterator {{}}
console.log(map.values()) // MapIterator {"三分钟学前端"}
如果想让 a 置为 null
时,该对象被回收,该怎么做喃?
WeakMap vs Map
ES6 思考到了这一点,推出了:WeakMap
。它对于值的援用都是不计入垃圾回收机制的,所以名字外面才会有一个 ”Weak”,示意这是弱援用(对对象的弱援用是指当该对象应该被 GC 回收时不会阻止 GC 的回收行为)。
Map
绝对于 WeakMap
:
Map
的键能够是任意类型,WeakMap
只承受对象作为键(null 除外),不承受其余类型的值作为键Map
的键实际上是跟内存地址绑定的,只有内存地址不一样,就视为两个键;WeakMap
的键是弱援用,键所指向的对象能够被垃圾回收,此时键是有效的Map
能够被遍历,WeakMap
不能被遍历
上面以 WeakMap
为例,看看它是怎么下面问题的:
var a = {};
var map = new WeakMap();
map.set(a, '三分钟学前端')
map.get(a)
a = null;
上例并不能看出什么?咱们通过 process.memoryUsage
测试一下:
//map.js
global.gc(); // 0 每次查问内存都先执行 gc()再 memoryUsage(),是为了确保垃圾回收,保障获取的内存应用状态精确
function usedSize() {const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
console.log(usedSize()); // 1 初始状态,执行 gc()和 memoryUsage()当前,heapUsed 值为 1.64M
var map = new Map();
var b = new Array(5 * 1024 * 1024);
map.set(b, 1);
global.gc();
console.log(usedSize()); // 2 在 Map 中退出元素 b,为一个 5*1024*1024 的数组后,heapUsed 为 41.82M 左右
b = null;
global.gc();
console.log(usedSize()); // 3 将 b 置为空当前,heapUsed 仍为 41.82M,阐明 Map 中的那个长度为 5 *1024*1024 的数组仍然存在
执行 node --expose-gc map.js
命令:
其中,--expose-gc
参数示意容许手动执行垃圾回收机制
// weakmap.js
function usedSize() {const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
global.gc(); // 0 每次查问内存都先执行 gc()再 memoryUsage(),是为了确保垃圾回收,保障获取的内存应用状态精确
console.log(usedSize()); // 1 初始状态,执行 gc()和 memoryUsage()当前,heapUsed 值为 1.64M
var map = new WeakMap();
var b = new Array(5 * 1024 * 1024);
map.set(b, 1);
global.gc();
console.log(usedSize()); // 2 在 Map 中退出元素 b,为一个 5*1024*1024 的数组后,heapUsed 为 41.82M 左右
b = null;
global.gc();
console.log(usedSize()); // 3 将 b 置为空当前,heapUsed 变成了 1.82M 左右,阐明 WeakMap 中的那个长度为 5 *1024*1024 的数组被销毁了
执行 node --expose-gc weakmap.js
命令:
下面代码中,只有内部的援用隐没,WeakMap 外部的援用,就会主动被垃圾回收革除。由此可见,有了它的帮忙,解决内存透露就会简略很多。
最初看一下 WeakMap
WeakMap
WeakMap 对象是一组键值对的汇合,其中的 键是弱援用对象,而值能够是任意。
留神,WeakMap 弱援用的只是键名,而不是键值。键值仍然是失常援用。
WeakMap 中,每个键对本人所援用对象的援用都是弱援用,在没有其余援用和该键援用同一对象,这个对象将会被垃圾回收(相应的 key 则变成有效的),所以,WeakMap 的 key 是不可枚举的。
属性:
- constructor:构造函数
办法:
- has(key):判断是否有 key 关联对象
- get(key):返回 key 关联对象(没有则则返回 undefined)
- set(key):设置一组 key 关联对象
- delete(key):移除 key 的关联对象
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
除了 WeakMap
还有 WeakSet
都是弱援用,能够被垃圾回收机制回收,能够用来保留 DOM 节点,不容易造成内存透露
另外还有 ES12 的 WeakRef
,感兴趣的能够理解下,今晚太晚了,之后更新
参考
你不晓得的 WeakMap
最初
本文首发自「三分钟学前端」,每天三分钟,进阶一个前端小 tip
面试题库
算法题库