趁着悠閒,在家學習 vue-next 源碼,注意到其對 WeakMap
的應用;你們應該都知道,新版本 Vue 與舊版本相比,實現機制從 defineProperty
轉變爲 Proxy
,卻可能不曾注意到細緻末節的差別。前端
ES6 以前,前端使用 Map 時,通常都是經過對象來模擬;對象的 key
值只能是字符串,即便傳入的 key
值不是字符串形式,也會被轉爲字符串,而 Map 沒有這種限制:vue
let o = {}; let o1 = {toString(){ return 1; }}; let o2 = {}; o[o1] = 1; o[o2] = 2; o; // {1: 1, [object Object]: 2}
key
只能是對象類型(null
除了 typeof
的時候被當成對象的 bug,任什麼時候候都不算對象):const m1 = new Map(); const wm = new WeakMap(); const k1 = { foo: 1 }; const k2 = 'k2'; m1.set(k1, 'v1'); m1.set(k2, 'v2') wm.set(k1, 'v1'); // wm.set(k2, 'v2'); // TypeError: Invalid value used as weak map key
key
不計入垃圾回收機制。WeakMap 的 key
所引用的對象都是弱引用,即垃圾回收機制不將該引用考慮在內;一旦 key
不被其餘地方引用,那麼就會被回收。正是由於 WeakMap 鍵名的不肯定性,它沒有 keys()
、values()
和 entries()
方法,也沒有 size
屬性;此外,WeakMap 尚未 clear()
方法(在最初的時候,是有這個方法的)。前面說了,WeakMap 相對於 Map 的一個重要區別就是其垃圾回收機制;空口無憑,下面用一段代碼來展現其回收效果(由於瀏覽器環境下,沒法經過代碼控制垃圾回收,因此如下代碼經過 Node.js 運行,node --expose-gc
來開啓手動清理):node
// 打印方法格式化 function format(value) { return `${(value / 1024 / 1024).toFixed(2)} M` } function print(m) { console.log(`HeapTotal: ${format(m.heapTotal)}`); console.log(`HeapUsed: ${format(m.heapUsed)}`); } // 手動垃圾回收 global.gc(); console.log('初始化:'); print(process.memoryUsage()); // node中查看內存狀態的方法,咱們目前只須要關注 heapTotal,heapUsed let vm = new WeakMap(); let key = new Array(20 * 1024 * 1024); vm.set(key, 'foo'); global.gc(); console.log('回收前:'); print(process.memoryUsage()); key = null; // key 置爲 null 以後,只被 vm 鍵名引用 global.gc(); console.log('回收後:'); print(process.memoryUsage()); // 上面代碼的運行結果: // node --expose-gc weakmap-test.js // 初始化: // HeapTotal: 4.52 M // HeapUsed: 1.86 M // 回收前: // HeapTotal: 166.52 M // HeapUsed: 161.71 M // 回收後: // HeapTotal: 10.52 M // HeapUsed: 1.71 M
若是將上述代碼中的 WeakMap 替換爲 Map 的話,運行結果爲:react
初始化: HeapTotal: 4.52 M HeapUsed: 1.86 M 回收前: HeapTotal: 166.52 M HeapUsed: 161.71 M 回收後: HeapTotal: 170.52 M HeapUsed: 161.71 M
使用 Map 時,因爲數組佔用的內存未被回收,HeapUsed 在垃圾回收先後無差別;而使用 WeakMap 時,在垃圾回收後,內存恢復。另外,咱們在代碼中建立了一個長度爲 20M 的空數組,但其佔用的內存卻約爲 160M,也就是說,每一個空元素佔用的內存大小爲 8 個字節。若是有興趣的話,能夠查看在 Chrome 中 JavaScript 數組到底佔用了多少內存?git
在瀏覽器中,能夠經過開發者工具來查看使用 WeakMap 和 Map 時對內存的影響:es6
const map = new WeakMap(); // 將 WeakMap 替換爲 Map 後,再次點擊 Take Heap Snapshot,耗時明顯增長;而且從圖中能夠看出,array 佔用的內存,並無被回收。 (function () { // 當即執行函數,執行完以後,除了 map/weakmap 以外,無其餘方式能夠訪問到;當其被做爲 map 的 key 時,內存不會回收;而做爲 WeakMap 的 key 時,執行完成後會被回收; const arr1 = new Array(20 * 1024 * 1024); const arr2 = new Array(20 * 1024 * 1024); const arr3 = new Array(20 * 1024 * 1024); const arr4 = new Array(20 * 1024 * 1024); const arr5 = new Array(20 * 1024 * 1024); const arr6 = new Array(20 * 1024 * 1024); const arr7 = new Array(20 * 1024 * 1024); const arr8 = new Array(20 * 1024 * 1024); const arr9 = new Array(20 * 1024 * 1024); const arr10 = new Array(20 * 1024 * 1024); const arr11 = new Array(20 * 1024 * 1024); const arr12 = new Array(20 * 1024 * 1024); const arr13 = new Array(20 * 1024 * 1024); const arr14 = new Array(20 * 1024 * 1024); const arr15 = new Array(20 * 1024 * 1024); const arr16 = new Array(20 * 1024 * 1024); const arr17 = new Array(20 * 1024 * 1024); const arr18 = new Array(20 * 1024 * 1024); map.set(arr1, 'arr1'); map.set(arr2, 'arr2'); map.set(arr3, 'arr3'); map.set(arr4, 'arr4'); map.set(arr5, 'arr5'); map.set(arr6, 'arr6'); map.set(arr7, 'arr7'); map.set(arr8, 'arr8'); map.set(arr9, 'arr9'); map.set(arr10, 'arr10'); map.set(arr11, 'arr11'); map.set(arr12, 'arr12'); map.set(arr13, 'arr13'); map.set(arr14, 'arr14'); map.set(arr15, 'arr15'); map.set(arr16, 'arr16'); map.set(arr17, 'arr17'); map.set(arr18, 'arr18'); })();
WeakMap 執行結果:
Map 執行結果:github
在前端項目中對 WeakMap 的應用場景,能夠參見阮一峯老師 WeakMap-的用途。web
參考:chrome