學習時間:2020.05.26
學習章節:《你不知道的 WeakMap》javascript
原文主要複習了「JavaScript垃圾回收機制」,「Map/WeakMap區別」和「WeakMap 屬性和方法」。這很好彌補被我忽視的知識點。
另外,咱們能夠經過原文,以相同方式再去學 Set/WeakSet,效果會更好,本文後面也會介紹到。
總結開始,先看原文大綱:
html
在開始介紹 WeakMap 以前,先複習一遍 JavaScript 中垃圾回收機制,這跟後面的 WeakMap/WeakSet 關係較大。
java
垃圾回收(Garbage Collection,縮寫爲GC))是一種自動的存儲器管理機制。當某個程序佔用的一部份內存空間再也不被這個程序訪問時,這個程序會藉助垃圾回收算法向操做系統歸還這部份內存空間。垃圾回收器能夠減輕程序員的負擔,也減小程序中的錯誤。垃圾回收最先起源於LISP語言。
目前許多語言如Smalltalk、Java、C#和D語言都支持垃圾回收器,咱們熟知的 JavaScript 具備自動垃圾回收機制。
在 JavaScript 中,原始類型的數據被分配到棧空間中,引用類型的數據會被分配到堆空間中。
**node
當函數 showName
調用完成後,經過下移 ESP(Extended Stack Pointer)指針,來銷燬 showName
函數,以後調用其餘函數時,將覆蓋掉舊內存,存放另外一個函數的執行上下文,實現垃圾回收。
圖片來自《瀏覽器工做原理與實踐》
程序員
堆中數據垃圾回收策略的基礎是:代際假說(The Generational Hypothesis)。即:算法
這兩個特色不只僅適用於 JavaScript,一樣適用於大多數的動態語言,如 Java、Python 等。
V8 引擎將堆空間分爲新生代(存放生存時間短的對象)和老生代(存放生存時間長的對象)兩個區域,並使用不一樣的垃圾回收器。
數組
不論是哪一種垃圾回收器,都使用相同垃圾回收流程:標記活動對象和非活動對象,回收非活動對象的內存,最後內存整理。
**瀏覽器
使用 Scavenge 算法處理,將新生代空間對半分爲兩個區域,一個對象區域,一個空閒區域。
圖片來自《瀏覽器工做原理與實踐》
執行流程:緩存
固然,這也存在一些問題:若複製操做的數據較大則影響清理效率。
JavaScript 引擎的解決方式是:將新生代區域設置得比較小,並採用對象晉升策略(通過兩次回收仍存活的對象,會被移動到老生區),避免由於新生代區域較小引發存活對象裝滿整個區域的問題。
函數
分爲:標記 - 清除(Mark-Sweep)算法,和標記 - 整理(Mark-Compact)算法。
a)標記 - 清除(Mark-Sweep)算法
過程:
圖片來自《瀏覽器工做原理與實踐》
b)標記 - 整理(Mark-Compact)算法
過程:
圖片來自《瀏覽器工做原理與實踐》
WeakMap
結構與 Map
結構相似,也是用於生成鍵值對的集合。
區別:
Map
對象的鍵能夠是任何類型,但 WeakMap
對象中的鍵只能是對象引用( null
除外);const map = new WeakMap(); map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key map.set(null, 2) // TypeError: Invalid value used as weak map key
WeakMap
不能包含無引用的對象,不然會被自動清除出集合(垃圾回收機制);WeakMap
對象沒有 size
屬性,是不可枚舉的,沒法獲取集合的大小。const map = new WeakMap(); const user1 = {name: 'leo'}; const user2 = {name: 'pingan'}; map.set(user1, 'good~'); map.set(user2, 'hello'); map.map(item => console.log(item)) //Uncaught TypeError: map.map is not a function
1.賦值和搜索操做都是 O(n) 的時間複雜度,由於這兩個操做都須要遍歷所有整個數組來進行匹配。
2.可能會致使內存泄漏,由於數組會一直引用着每一個鍵和值。
相比之下, WeakMap
持有的是每一個鍵對象的 「弱引用」,這意味着在沒有其餘引用存在時垃圾回收能正確進行。 原生 WeakMap
的結構是特殊且有效的,其用於映射的 key 只有在其沒有被回收時纔是有效的。
當數據量越大,則垃圾回收效果越明顯。
經過命令行執行 node --expose-gc weakmap.js
查看對比效果。
其中 --expose-gc
參數表示容許手動執行垃圾回收機制。
// weakmap.js const objNum = 10 * 1024 * 1024; const useType = 1; // 修改 useType 值來測試Map和WeakMap const curType = useType == 1 ?"【Map】" : "【WeakMap】"; let arr = new Array(objNum); function usageSize() { const used = process.memoryUsage().heapUsed; return Math.round((used / 1024 / 1024) * 100) / 100 + "M"; } if (useType == 1) { global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); const map = new Map(); map.set(arr, 1); global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); arr = null; global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); console.log("=====") } else { global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); const map = new WeakMap(); global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); arr = null; global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); console.log("=====") }
WeakMap
對象是一組鍵/值對的集合,其中的鍵是 弱引用 的。
WeakMap 的 key 只能是 Object 類型。
原始數據類型是不能做爲 key 的(好比 Symbol)。WeakMap
只有四個方法可用:get()
、set()
、has()
、delete()
。
**
具體屬性和方法介紹,可查看 《MDN WeakMap》。
原文中介紹了「經過 WeakMap 緩存計算結果」和「在 WeakMap 中保留私有數據」兩種應用場景。
另外還有一種比較常見的場景:以 DOM節點做爲鍵名的場景。
場景1:當咱們想要爲DOM添加數據時,可以使用 WeakMap
。
好處在於,當DOM元素移除時,對應 WeakMap 記錄也會自動移除:
<div id="WeakMap"></div>
const wm = new WeakMap(); const weakMap = document.getElementById('WeakMap'); wm.set(weakMap, 'some information'); wm.get(weakMap) //"some information"
場景2:當咱們想要爲DOM元素添加事件監聽時,可以使用 WeakMap
。
<button id="button1">按鈕1</button> <button id="button2">按鈕2</button>
const button1 = document.getElementById('button1'); const button2 = document.getElementById('button2'); const handler1 = () => { console.log("button1 被點擊") }; const handler2 = () => { console.log("button2 被點擊") }; // 代碼1 button1.addEventListener('click', handler1, false); button2.addEventListener('click', handler2, false); // 代碼2 const listener = new WeakMap(); listener.set(button1, handler1); listener.set(button2, handler2); button1.addEventListener('click', listener.get(button1), false); button2.addEventListener('click', listener.get(button2), false);
代碼2比起代碼1的好處是:因爲監聽函數是放在 WeakMap 裏面,
則一旦 DOM 對象button1 / button2消失,與它綁定的監聽函數handler1和handler2 也會自動消失。
WeakSet
結構與 Set
相似,也是不重複的值的集合。
區別:
WeakSet
的成員只能是對象,而不能是其餘類型的值;const ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set ws.add(Symbol()) // TypeError: invalid value used in weak set
WeakSet
中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet
對該對象的引用;WeakSet
對象沒有 size
屬性,是不可枚舉的,沒法獲取集合的大小。經過命令行執行 node --expose-gc weakset.js
查看對比效果。
// weakset.js const objNum = 5000 * 1024; const useType = 1; const curType = useType == 1 ?"【Set】" : "【WeakSet】"; let obj = []; for (let k = 0; k < objNum; k++) { obj[k] = {} } function usageSize() { const used = process.memoryUsage().heapUsed; return Math.round((used / 1024 / 1024) * 100) / 100 + "M"; } if (useType == 1) { global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); const sets = new Set([...obj]); global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); obj = null; global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); console.log("=====") } else { global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); const sets = new WeakSet(obj); global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); obj = null; global.gc(); console.log(objNum + '個' + curType + '佔用內存:' + usageSize()); console.log("=====") }
本文首先複習了《你不知道的 WeakMap》中核心知識點,從新回顧了「垃圾回收機制」,「Map VS WeakMap」和「WeakMap 介紹和應用」,最後延伸複習了「Set/WeakSet」相關知識點。在實際業務開發中,最好也能考慮垃圾回收機制的合理使用,這也是提高產品性能的一個很是經常使用的方式。