JS WeakMap應該什麼時候使用

鑫空間 2021-08-15 17:13:20 阅读数:132

本文一共[544]字,预计阅读时长:1分钟~
js weakmap 使用

紅梅葡萄占比特圖

一、從哪裏開始呢?

算了,還是先說結論吧。

二、WeakMap什麼時候用?

首先,大家要明白,就算沒有 WeakMap,JS的世界也是照常運轉,WeakMap在JS世界的地比特,就和其名稱一樣——“weak”,弱。

屬於那種有作用,但是並不在關鍵比特置幹大事的人,有點類似於一些CSS功能選擇器,雖然使用更簡單更方便,但是平常使用JS實現的交互效果挺好的,所以並不那麼流行與普及。

WeakMap的作用就是可以更有效的垃圾回收、釋放內存。

我們平常開發的Web應用都是如此的簡單,就算代碼很垃圾,吃了很多內存那又如何,頁面照樣流暢,用戶照樣無感知。

所以,對於絕大多數開發者和應用程序而言,WeakMap的商業價值是很低的。

這麼一說,似乎WeakMap沒什麼好學的。

其實不然。

如果是超大型應用,或者用戶基數龐大的產品,或者是服務器這種負載較高的場景,對於內存管理要求就很高,此時WeakMap的優勢就可以體現。

這其實有點悖論的味道在裏面。

上述所有需要用到WeakMap的場景都一定是需要資深前端開發參與的場景,而如果你連WeakMap都不知道,就談不上資深,挑上面的大梁可能會閃著腰。

也就是,你學會了WeakMap以及類似的JS知識點,才有機會參與必須要使用這些JS特性的項目中,完成自我價值的證明與貢獻。

所以,沒有什麼知識是沒用的,只是時機的問題,所謂厚積薄發,就是這樣一個意思。

垃圾?內存?

就內存管理而言,JS開發人員可以歸為下面幾種:

  1. 內存?什麼內存?我JS想怎麼寫就怎麼寫,運行不挺好的!反正瀏覽器頁面關掉什麼都沒了。
  2. 恩,這裏這個對象之後沒用了,我可以設置為null。意識是好的,設置也設置了,就是內存究竟有沒有釋放不得而知,看運氣。
  3. 這裏設置null不行,因為對象在其他地方也引用了,其他引用的地方也要删除,屬於理解比較深刻的,JS基本功很紮實的。

舉個大家比較容易懂的例子,已知頁面中有個DOM元素,HTML結構如下:

<img id="img" src="//inotgo.com/imagesLocal/202108/15/20210815171305702e_4.jpg">

然後,需要删除此DOM元素,小明是這麼處理的:

var eleImage = document.getElementById('img');
eleImage.remove();

這個處理有沒有什麼問題?

從效果上來看,完全解决了需求。

但是實際上,雖然頁面中的圖片元素删除了,但是內存中的這個DOM對象依然存在的。

我們不妨這樣測試下:

var eleImage = document.getElementById('img');
eleImage.remove();
setTimeout(() => {
document.body.append(eleImage);
}, 2000);

就會看到圖片被删除了(如下GIF錄屏),然後過了2秒鐘又出現在了頁面上,因為eleImage還在內存中,並未清除,不會被回收。

删除後2秒又出現

所以,如果確定eleImage不再需要,需要多執行一句,同時設為 null。

var eleImage = document.getElementById('img');
eleImage.remove();
eleImage = null;

這樣,JS在執行垃圾回收的時候,就會把eleImage這個垃圾收回,釋放內存。

大家可以看看自己是不關心內存的那類JS開發,還是會注意釋放不需要的內存的JS開發。

OK,事情還沒完,有時候,設置eleImage為null,並不能真正回收內存。

例如實際開發,有時候需要記住初始的outerHTML字符,方便還原。為了和原始DOM產生關聯,有些開發就把DOM元素和outerHTML字符串放在同一個數組中進行管理:

var eleImage = document.getElementById('img');
var storage = {
arrDom: [eleImage, eleImage.outerHTML]
};
eleImage.remove();
eleImage = null;

此時,eleImage這個DOM對象其實還在內存中。因為 eleImage 被 storage.arrDom 引用了,即使eleImage設為null也無法將內存徹底釋放。

測試下:

var eleImage = document.getElementById('img');
var storage = {
arrDom: [eleImage, eleImage.outerHTML]
};
eleImage.remove();
eleImage = null;
setTimeout(() => {
document.body.append(storage.arrDom[0]);
}, 2000);

同樣可以看到,圖片被删除後,2秒後又出現了。

這個還是看實時效果吧。

點擊後面的按鈕查看效果:

此時,要想完全把圖像的內存釋放,還需要執行下面這行:

storage.arrDom[0] = null;

大家試想下,如果是你,可以釋放內存做到這一步嗎?如果可以做到,那你就可以歸類為資深的JS前端開發那一類了。

但是,純靠技術手段,人工識別哪些地方的內存要釋放,實在是太累了。就算懂行的人有時候嫌麻煩,都懶得去管理,那有沒有什麼手段,我只要變量設為null,所有有引用的地方的內存都自動釋放呢?

這就可以考慮使用WeakMap了。

使用場景

上面的例子,如果使用WeakMap實現回是怎樣的呢?

代碼說話:

var eleImage = document.getElementById('img');
var storeMap = new WeakMap();
storeMap.set(eleImage, eleImage.outerHTML);
eleImage.remove();
eleImage = null;

同樣是緩存圖片的outerHTML數據,但是這裏使用了 WeakMap 對象,將 eleImage 作為一個弱鍵,這樣,eleImage一旦設置為 null,所有相關的數據都會被釋放。

究竟內存釋放沒有,上面的例子不好測試,一是要借助工具,二是內存變化很小,看不出來。

不過,我們可以使用Map對象對比下,如果是Map對象,eleImage作為鍵,那就是强引用,是一直在內存中的,例如:

var eleImage = document.getElementById('img');
var storeMap = new Map();
storeMap.set(eleImage, eleImage.outerHTML);
eleImage.remove();
eleImage = null;
setTimeout(() => {
document.body.append(storeMap.keys().next().value);
}, 2000);

可以看到,雖然eleImage remove掉了,還設為了null,但是依然在內存中,可以append到頁面中。

根據上面的分析和描述,我們可以得出下面的結論。

結論

當我們需要在某個對象上臨時存放數據的時候,請使用WeakMap,尤其對於JS理解不是很深刻的開發人員,更是如此,因為省心,不要關心經常掛在嘴邊的“內存泄露”問題。

因為到時候只需要删除該對象,所有相關的引用和關聯的內存都會被釋放。

也就是,雖然我看不懂代碼是怎麼執行的,但是我這麼寫的,性能就是好,逼格就是高!

看不懂

三、回到WeakMap語法本身

說了這麼多,是時候介紹 WeakMap 語法的真身了。

語法

let myWm = new WeakMap()

此時,myWm就是一個新的WeakMap對象,包括了下面這些方法:

// 删除鍵
myWm.delete(key);
// 設置鍵和值
myWm.set(key, value);
// 是否包含某鍵
myWm.has(key);
// 獲取鍵對應的值
myWm.get(key);

說明

  1. key只能是對象,不能是原始數據類型(字符串、數字、true或false,null,undefined,symbol等類型)
  2. WeakMap中的鍵是無法枚舉的

key只能是對象

下面的用法都是可以的:

myWm.set([], 1);
myWm.set(new Date(), '鑫空間');
myWm.set(()=>{}, 1);
myWm.set(document.createElement('by-zhangxinxu'), 1);

但是如果key不是對象,而是字符串之類的基本類型,就會報錯,例如:

// 會報錯
myWm.set('css新世界', true);

此時會報下面的錯誤:

“TypeError: Invalid value used as weak map key

因此WeakMap適合用在在對象上臨時緩存數據的場景。

key無法枚舉

不同於Map對象,Map對象是可以枚舉的,有keys()values()entries()方法,還可以使用forEach遍曆。

但是WeakMap無法枚舉,WeakMap的這個特性也可以用來模擬私有屬性。

const myWm = new WeakMap();
class Fish {
constructor(name) {
myWm.set(this, {
_fishbone: ['草魚', '鯽魚', '青魚', '鯉魚', '鰱魚', '鱅魚', '鯿魚', '翹嘴', '餐條'],
});
this.name = name;
}
isBone() {
return myWm.get(this)._fishbone.includes(this.name);
}
}
// 測試,買了兩條魚
let fish1 = new Fish('草魚');
let fish2 = new Fish('回魚');
// 返回 true,有刺
console.log(fish1.isBone());
// 返回 false,沒有肌間刺
console.log(fish2.isBone());

上面的代碼中,_fishbone雖然和Fish對象相關聯,但是卻無法通過Fish對象直接獲取。

如果不知道名稱,也無法通過myWm遍曆出來。

關於WeakMap更詳細的語法介紹和示意可參考MDN文檔:MDN WeakMap

四、結束語

考考大家,我昨天釣的下面3種魚,哪種魚有肌間刺,哪幾種魚沒有肌間刺?

釣貨

好,本文內容就上面這些。

如果文中有錶述不准確的地方,或者有所遺漏,歡迎指正。

如果您覺得文章不錯,也歡迎分享。

(本篇完)

版权声明:本文为[鑫空間]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815171305702e.html