ES6 系列之 WeakMap的使用示例
前言
我們先從 WeakMap 的特性說起,然后聊聊 WeakMap 的一些應(yīng)用場(chǎng)景。
特性
1. WeakMap 只接受對(duì)象作為鍵名
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
2. WeakMap 的鍵名所引用的對(duì)象是弱引用
這句話其實(shí)讓我非常費(fèi)解,我個(gè)人覺得這句話真正想表達(dá)的意思應(yīng)該是:
WeakMaps hold "weak" references to key objects,
翻譯過來應(yīng)該是 WeakMaps 保持了對(duì)鍵名所引用的對(duì)象的弱引用。
我們先聊聊弱引用:
在計(jì)算機(jī)程序設(shè)計(jì)中,弱引用與強(qiáng)引用相對(duì),是指不能確保其引用的對(duì)象不會(huì)被垃圾回收器回收的引用。 一個(gè)對(duì)象若只被弱引用所引用,則被認(rèn)為是不可訪問(或弱可訪問)的,并因此可能在任何時(shí)刻被回收。
在 JavaScript 中,一般我們創(chuàng)建一個(gè)對(duì)象,都是建立一個(gè)強(qiáng)引用:
var obj = new Object();
只有當(dāng)我們手動(dòng)設(shè)置 obj = null 的時(shí)候,才有可能回收 obj 所引用的對(duì)象。
而如果我們能創(chuàng)建一個(gè)弱引用的對(duì)象:
// 假設(shè)可以這樣創(chuàng)建一個(gè) var obj = new WeakObject();
我們什么都不用做,只用靜靜的等待垃圾回收機(jī)制執(zhí)行,obj 所引用的對(duì)象就會(huì)被回收。
我們?cè)賮砜纯催@句:
WeakMaps 保持了對(duì)鍵名所引用的對(duì)象的弱引用
正常情況下,我們舉個(gè)例子:
const key = new Array(5 * 1024 * 1024); const arr = [ [key, 1] ];
使用這種方式,我們其實(shí)建立了 arr 對(duì) key 所引用的對(duì)象(我們假設(shè)這個(gè)真正的對(duì)象叫 Obj)的強(qiáng)引用。
所以當(dāng)你設(shè)置 key = null
時(shí),只是去掉了 key 對(duì) Obj 的強(qiáng)引用,并沒有去除 arr 對(duì) Obj 的強(qiáng)引用,所以 Obj 還是不會(huì)被回收掉。
Map 類型也是類似:
let map = new Map(); let key = new Array(5 * 1024 * 1024); // 建立了 map 對(duì) key 所引用對(duì)象的強(qiáng)引用 map.set(key, 1); // key = null 不會(huì)導(dǎo)致 key 的原引用對(duì)象被回收 key = null;
我們可以通過 Node 來證明一下這個(gè)問題:
// 允許手動(dòng)執(zhí)行垃圾回收機(jī)制 node --expose-gc global.gc(); // 返回 Nodejs 的內(nèi)存占用情況,單位是 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 // 這句話其實(shí)是無用的,因?yàn)?key 已經(jīng)是 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
這個(gè)時(shí)候就要說到 WeakMap 了:
const wm = new WeakMap(); let key = new Array(5 * 1024 * 1024); wm.set(key, 1); key = null;
當(dāng)我們?cè)O(shè)置 wm.set(key, 1) 時(shí),其實(shí)建立了 wm 對(duì) key 所引用的對(duì)象的弱引用,但因?yàn)?code> let key = new Array(5 * 1024 * 1024) 建立了 key 對(duì)所引用對(duì)象的強(qiáng)引用,被引用的對(duì)象并不會(huì)被回收,但是當(dāng)我們?cè)O(shè)置 key = null
的時(shí)候,就只有 wm 對(duì)所引用對(duì)象的弱引用,下次垃圾回收機(jī)制執(zhí)行的時(shí)候,該引用對(duì)象就會(huì)被回收掉。
我們用 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 可以幫你省掉手動(dòng)刪除對(duì)象關(guān)聯(lián)數(shù)據(jù)的步驟,所以當(dāng)你不能或者不想控制關(guān)聯(lián)數(shù)據(jù)的生命周期時(shí)就可以考慮使用 WeakMap。
總結(jié)這個(gè)弱引用的特性,就是 WeakMaps 保持了對(duì)鍵名所引用的對(duì)象的弱引用,即垃圾回收機(jī)制不將該引用考慮在內(nèi)。只要所引用的對(duì)象的其他引用都被清除,垃圾回收機(jī)制就會(huì)釋放該對(duì)象所占用的內(nèi)存。也就是說,一旦不再需要,WeakMap 里面的鍵名對(duì)象和所對(duì)應(yīng)的鍵值對(duì)會(huì)自動(dòng)消失,不用手動(dòng)刪除引用。
也正是因?yàn)檫@樣的特性,WeakMap 內(nèi)部有多少個(gè)成員,取決于垃圾回收機(jī)制有沒有運(yùn)行,運(yùn)行前后很可能成員個(gè)數(shù)是不一樣的,而垃圾回收機(jī)制何時(shí)運(yùn)行是不可預(yù)測(cè)的,因此 ES6 規(guī)定 WeakMap 不可遍歷。
所以 WeakMap 不像 Map,一是沒有遍歷操作(即沒有keys()、values()和entries()方法),也沒有 size 屬性,也不支持 clear 方法,所以 WeakMap只有四個(gè)方法可用:get()、set()、has()、delete()。
應(yīng)用
1. 在 DOM 對(duì)象上保存相關(guān)數(shù)據(jù)
傳統(tǒng)使用 jQuery 的時(shí)候,我們會(huì)通過 $.data() 方法在 DOM 對(duì)象上儲(chǔ)存相關(guān)信息(就比如在刪除按鈕元素上儲(chǔ)存帖子的 ID 信息),jQuery 內(nèi)部會(huì)使用一個(gè)對(duì)象管理 DOM 和對(duì)應(yīng)的數(shù)據(jù),當(dāng)你將 DOM 元素刪除,DOM 對(duì)象置為空的時(shí)候,相關(guān)聯(lián)的數(shù)據(jù)并不會(huì)被刪除,你必須手動(dòng)執(zhí)行 $.removeData() 方法才能刪除掉相關(guān)聯(lián)的數(shù)據(jù),WeakMap 就可以簡(jiǎn)化這一操作:
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;
2. 數(shù)據(jù)緩存
從上一個(gè)例子,我們也可以看出,當(dāng)我們需要關(guān)聯(lián)對(duì)象和數(shù)據(jù),比如在不修改原有對(duì)象的情況下儲(chǔ)存某些屬性或者根據(jù)對(duì)象儲(chǔ)存一些計(jì)算的值等,而又不想管理這些數(shù)據(jù)的死活時(shí)非常適合考慮使用 WeakMap。數(shù)據(jù)緩存就是一個(gè)非常好的例子:
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; } }
3. 私有屬性
WeakMap 也可以被用于實(shí)現(xiàn)私有變量,不過在 ES6 中實(shí)現(xiàn)私有變量的方式有很多種,這只是其中一種:
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;
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript動(dòng)態(tài)改變div屬性的實(shí)現(xiàn)方法
這篇文章主要介紹了JavaScript動(dòng)態(tài)改變div屬性的實(shí)現(xiàn)方法,涉及javascript操作頁面div元素屬性的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07js動(dòng)態(tài)生成按鈕并動(dòng)態(tài)生成8位隨機(jī)數(shù)
用js生成按鈕,動(dòng)態(tài)生成8位隨機(jī)數(shù)的腳本2008-09-09mockjs,json-server一起搭建前端通用的數(shù)據(jù)模擬框架教程
下面小編就為大家分享一篇mockjs,json-server一起搭建前端通用的數(shù)據(jù)模擬框架教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12JS實(shí)現(xiàn)多物體運(yùn)動(dòng)的方法詳解
這篇文章主要介紹了JS實(shí)現(xiàn)多物體運(yùn)動(dòng)的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了javascript實(shí)現(xiàn)多物體運(yùn)動(dòng)的原理與相關(guān)操作技巧,需要的朋友可以參考下2018-01-01利用JavaScript緩存遠(yuǎn)程竊取Wi-Fi密碼的思路詳解
這篇文章主要介紹了利用JavaScript緩存遠(yuǎn)程竊取Wi-Fi密碼的方法,本文通過實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11