WeakMap 和 Map 的區(qū)別,WeakMap 原理,為什么能被 GC?
垃圾回收機(jī)制
我們知道,程序運(yùn)行中會有一些垃圾數(shù)據(jù)不再使用,需要及時釋放出去,如果我們沒有及時釋放,這就是內(nèi)存泄露
JS 中的垃圾數(shù)據(jù)都是由垃圾回收(Garbage Collection,縮寫為 GC)器自動回收的,不需要手動釋放,它是如何做的喃?
很簡單,JS 引擎中有一個后臺進(jìn)程稱為垃圾回收器,它監(jiān)視所有對象,觀察對象是否可被訪問,然后按照固定的時間間隔周期性的刪除掉那些不可訪問的對象即可
現(xiàn)在各大瀏覽器通常用采用的垃圾回收有兩種方法:
引用計數(shù) 標(biāo)記清除
引用計數(shù)
最早最簡單的垃圾回收機(jī)制,就是給一個占用物理空間的對象附加一個引用計數(shù)器,當(dāng)有其它對象引用這個對象時,這個對象的引用計數(shù)加一,反之解除時就減一,當(dāng)該對象引用計數(shù)為 0 時就會被回收。
該方式很簡單,但會引起內(nèi)存泄漏:
// 循環(huán)引用的問題
function temp(){
var a={};
var b={};
a.o = b;
b.o = a;
}
這種情況下每次調(diào)用 temp 函數(shù),a 和 b 的引用計數(shù)都是 2 ,會使這部分內(nèi)存永遠(yuǎn)不會被釋放,即內(nèi)存泄漏?,F(xiàn)在已經(jīng)很少使用了,只有低版本的 IE 使用這種方式。
標(biāo)記清除
V8 中主垃圾回收器就采用標(biāo)記清除法進(jìn)行垃圾回收。主要流程如下:
標(biāo)記:遍歷調(diào)用棧,看老生代區(qū)域堆中的對象是否被引用,被引用的對象標(biāo)記為活動對象,沒有被引用的對象(待清理)標(biāo)記為垃圾數(shù)據(jù)。 垃圾清理:將所有垃圾數(shù)據(jù)清理掉

(圖片來源:How JavaScript works: memory management + how to handle 4 common memory leaks)
在我們的開發(fā)過程中,如果我們想要讓垃圾回收器回收某一對象,就將對象的引用直接設(shè)置為 null
var a = {}; // {} 可訪問,a 是其引用
a = null; // 引用設(shè)置為 null
// {} 將會被從內(nèi)存里清理出去
但如果一個對象被多次引用時,例如作為另一對象的鍵、值或子元素時,將該對象引用設(shè)置為 null 時,該對象是不會被回收的,依然存在
var a = {};
var arr = [a];
a = null;
console.log(arr)
// [{}]
如果作為 Map 的鍵喃?
var a = {};
var map = new Map();
map.set(a, '三分鐘學(xué)前端')
a = null;
console.log(map.keys()) // MapIterator {{}}
console.log(map.values()) // MapIterator {"三分鐘學(xué)前端"}
如果想讓 a 置為 null 時,該對象被回收,該怎么做喃?
WeakMap vs Map
ES6 考慮到了這一點(diǎn),推出了:WeakMap 。它對于值的引用都是不計入垃圾回收機(jī)制的,所以名字里面才會有一個"Weak",表示這是弱引用(對對象的弱引用是指當(dāng)該對象應(yīng)該被GC回收時不會阻止GC的回收行為)。
Map 相對于 WeakMap :
Map的鍵可以是任意類型,WeakMap只接受對象作為鍵(null除外),不接受其他類型的值作為鍵Map的鍵實(shí)際上是跟內(nèi)存地址綁定的,只要內(nèi)存地址不一樣,就視為兩個鍵;WeakMap的鍵是弱引用,鍵所指向的對象可以被垃圾回收,此時鍵是無效的Map可以被遍歷,WeakMap不能被遍歷
下面以 WeakMap 為例,看看它是怎么上面問題的:
var a = {};
var map = new WeakMap();
map.set(a, '三分鐘學(xué)前端')
map.get(a)
a = null;
上例并不能看出什么?我們通過 process.memoryUsage 測試一下:
//map.js
global.gc(); // 0 每次查詢內(nèi)存都先執(zhí)行g(shù)c()再memoryUsage(),是為了確保垃圾回收,保證獲取的內(nèi)存使用狀態(tài)準(zhǔn)確
function usedSize() {
const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
console.log(usedSize()); // 1 初始狀態(tài),執(zhí)行g(shù)c()和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 的數(shù)組后,heapUsed為41.82M左右
b = null;
global.gc();
console.log(usedSize()); // 3 將b置為空以后,heapUsed 仍為41.82M,說明Map中的那個長度為5*1024*1024的數(shù)組依然存在
執(zhí)行 node --expose-gc map.js 命令:

其中,--expose-gc 參數(shù)表示允許手動執(zhí)行垃圾回收機(jī)制
// weakmap.js
function usedSize() {
const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
global.gc(); // 0 每次查詢內(nèi)存都先執(zhí)行g(shù)c()再memoryUsage(),是為了確保垃圾回收,保證獲取的內(nèi)存使用狀態(tài)準(zhǔn)確
console.log(usedSize()); // 1 初始狀態(tài),執(zhí)行g(shù)c()和 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 的數(shù)組后,heapUsed為41.82M左右
b = null;
global.gc();
console.log(usedSize()); // 3 將b置為空以后,heapUsed 變成了1.82M左右,說明WeakMap中的那個長度為5*1024*1024的數(shù)組被銷毀了
執(zhí)行 node --expose-gc weakmap.js 命令:

上面代碼中,只要外部的引用消失,WeakMap 內(nèi)部的引用,就會自動被垃圾回收清除。由此可見,有了它的幫助,解決內(nèi)存泄漏就會簡單很多。
最后看一下 WeakMap
WeakMap
WeakMap 對象是一組鍵值對的集合,其中的鍵是弱引用對象,而值可以是任意。
注意,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。
WeakMap 中,每個鍵對自己所引用對象的引用都是弱引用,在沒有其他引用和該鍵引用同一對象,這個對象將會被垃圾回收(相應(yīng)的key則變成無效的),所以,WeakMap 的 key 是不可枚舉的。
屬性:
constructor:構(gòu)造函數(shù)
方法:
has(key):判斷是否有 key 關(guān)聯(lián)對象 get(key):返回key關(guān)聯(lián)對象(沒有則則返回 undefined) set(key):設(shè)置一組key關(guān)聯(lián)對象 delete(key):移除 key 的關(guān)聯(lián)對象
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 都是弱引用,可以被垃圾回收機(jī)制回收,可以用來保存DOM節(jié)點(diǎn),不容易造成內(nèi)存泄漏
另外還有 ES12 的 WeakRef ,感興趣的可以了解下,今晚太晚了,之后更新
參考
你不知道的 WeakMap
來自:https://github.com/Advanced-Frontend/Daily-Interview-Question
