乐趣区

关于javascript:WeakMap-和-Map-的区别WeakMap-原理为什么能被-GC

垃圾回收机制

咱们晓得,程序运行中会有一些垃圾数据不再应用,须要及时开释进来,如果咱们没有及时开释,这就是内存泄露

JS 中的垃圾数据都是由垃圾回收(Garbage Collection,缩写为 GC)器主动回收的,不须要手动开释,它是如何做的喃?

很简略,JS 引擎中有一个后盾过程称为垃圾回收器,它监督所有对象,察看对象是否可被拜访,而后依照固定的工夫距离周期性的删除掉那些不可拜访的对象即可

当初各大浏览器通常用采纳的垃圾回收有两种办法:

  • 援用计数
  • 标记革除

援用计数

最早最简略的垃圾回收机制,就是给一个占用物理空间的对象附加一个援用计数器,当有其它对象援用这个对象时,这个对象的援用计数加一,反之解除时就减一,当该对象援用计数为 0 时就会被回收。

该形式很简略,但会引起内存透露:

// 循环援用的问题
function temp(){var a={};
    var b={};
    a.o = b;
    b.o = a;
}

这种状况下每次调用 temp 函数,ab 的援用计数都是 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

面试题库
算法题库

退出移动版