咱們先從 WeakMap 的特性提及,而後聊聊 WeakMap 的一些應用場景。node
const map = new WeakMap();
map.set(1, 2);
// TypeError: Invalid value used as weak map key
map.set(null, 2);
// TypeError: Invalid value used as weak map key
複製代碼
這句話其實讓我很是費解,我我的以爲這句話真正想表達的意思應該是:git
WeakMaps hold "weak" references to key objects,github
翻譯過來應該是 WeakMaps 保持了對鍵名所引用的對象的弱引用。緩存
咱們先聊聊弱引用:異步
在計算機程序設計中,弱引用與強引用相對,是指不能確保其引用的對象不會被垃圾回收器回收的引用。 一個對象若只被弱引用所引用,則被認爲是不可訪問(或弱可訪問)的,並所以可能在任什麼時候刻被回收。函數
在 JavaScript 中,通常咱們建立一個對象,都是創建一個強引用:ui
var obj = new Object();
複製代碼
只有當咱們手動設置 obj = null
的時候,纔有可能回收 obj 所引用的對象。this
而若是咱們能建立一個弱引用的對象:spa
// 假設能夠這樣建立一個
var obj = new WeakObject();
複製代碼
咱們什麼都不用作,只用靜靜的等待垃圾回收機制執行,obj 所引用的對象就會被回收。翻譯
咱們再來看看這句:
WeakMaps 保持了對鍵名所引用的對象的弱引用
正常狀況下,咱們舉個例子:
const key = new Array(5 * 1024 * 1024);
const arr = [
[key, 1]
];
複製代碼
使用這種方式,咱們其實創建了 arr 對 key 所引用的對象(咱們假設這個真正的對象叫 Obj)的強引用。
因此當你設置 key = null
時,只是去掉了 key 對 Obj 的強引用,並無去除 arr 對 Obj 的強引用,因此 Obj 仍是不會被回收掉。
Map 類型也是相似:
let map = new Map();
let key = new Array(5 * 1024 * 1024);
// 創建了 map 對 key 所引用對象的強引用
map.set(key, 1);
// key = null 不會致使 key 的原引用對象被回收
key = null;
複製代碼
咱們能夠經過 Node 來證實一下這個問題:
// 容許手動執行垃圾回收機制
node --expose-gc
global.gc();
// 返回 Nodejs 的內存佔用狀況,單位是 bytes
process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4M
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46751472 注意這裏大約是 44.6M
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M
// 這句話實際上是無用的,由於 key 已是 null 了
map.delete(key);
global.gc();
process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M
複製代碼
若是你想要讓 Obj 被回收掉,你須要先 delete(key)
而後再 key = null
:
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
map.delete(key);
key = null;
複製代碼
咱們依然經過 Node 證實一下:
node --expose-gc
global.gc();
process.memoryUsage(); // heapUsed: 4638376 ≈ 4.4M
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46727816 ≈ 44.6M
map.delete(key);
global.gc();
process.memoryUsage(); // heapUsed: 46748352 ≈ 44.6M
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 4808064 ≈ 4.6M
複製代碼
這個時候就要說到 WeakMap 了:
const wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
key = null;
複製代碼
當咱們設置 wm.set(key, 1)
時,其實創建了 wm 對 key 所引用的對象的弱引用,但由於 let key = new Array(5 * 1024 * 1024)
創建了 key 對所引用對象的強引用,被引用的對象並不會被回收,可是當咱們設置 key = null
的時候,就只有 wm 對所引用對象的弱引用,下次垃圾回收機制執行的時候,該引用對象就會被回收掉。
咱們用 Node 證實一下:
node --expose-gc
global.gc();
process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4M
const wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M
複製代碼
因此 WeakMap 能夠幫你省掉手動刪除對象關聯數據的步驟,因此當你不能或者不想控制關聯數據的生命週期時就能夠考慮使用 WeakMap。
總結這個弱引用的特性,就是 WeakMaps 保持了對鍵名所引用的對象的弱引用,即垃圾回收機制不將該引用考慮在內。只要所引用的對象的其餘引用都被清除,垃圾回收機制就會釋放該對象所佔用的內存。也就是說,一旦再也不須要,WeakMap 裏面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。
也正是由於這樣的特性,WeakMap 內部有多少個成員,取決於垃圾回收機制有沒有運行,運行先後極可能成員個數是不同的,而垃圾回收機制什麼時候運行是不可預測的,所以 ES6 規定 WeakMap 不可遍歷。
因此 WeakMap 不像 Map,一是沒有遍歷操做(即沒有keys()、values()和entries()方法),也沒有 size 屬性,也不支持 clear 方法,因此 WeakMap只有四個方法可用:get()、set()、has()、delete()。
傳統使用 jQuery 的時候,咱們會經過 $.data()
方法在 DOM 對象上儲存相關信息(就好比在刪除按鈕元素上儲存帖子的 ID 信息),jQuery 內部會使用一個對象管理 DOM 和對應的數據,當你將 DOM 元素刪除,DOM 對象置爲空的時候,相關聯的數據並不會被刪除,你必須手動執行 $.removeData()
方法才能刪除掉相關聯的數據,WeakMap 就能夠簡化這一操做:
let wm = new WeakMap(), element = document.querySelector(".element");
wm.set(element, "data");
let value = wm.get(elemet);
console.log(value); // data
element.parentNode.removeChild(element);
element = null;
複製代碼
從上一個例子,咱們也能夠看出,當咱們須要關聯對象和數據,好比在不修改原有對象的狀況下儲存某些屬性或者根據對象儲存一些計算的值等,而又不想管理這些數據的死活時很是適合考慮使用 WeakMap。數據緩存就是一個很是好的例子:
const cache = new WeakMap();
function countOwnKeys(obj) {
if (cache.has(obj)) {
console.log('Cached');
return cache.get(obj);
} else {
console.log('Computed');
const count = Object.keys(obj).length;
cache.set(obj, count);
return count;
}
}
複製代碼
WeakMap 也能夠被用於實現私有變量,不過在 ES6 中實現私有變量的方式有不少種,這只是其中一種:
const privateData = new WeakMap();
class Person {
constructor(name, age) {
privateData.set(this, { name: name, age: age });
}
getName() {
return privateData.get(this).name;
}
getAge() {
return privateData.get(this).age;
}
}
export default Person;
複製代碼
ES6 系列目錄地址:github.com/mqyqingfeng…
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級做用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。