# 什麼是 WeakMap object
WeakMap object 是一個 ES6 新增的物件,是一個由 key/value 組成的物件,其中 key 屬於弱引用(weak reference),並且必須是物件形式(object)。
# 弱引用
弱引用在程式設計中是用來做垃圾回收機制(釋放記憶體空間)用的,一個對象若只被弱引用所引用,則被認為是不可訪問(或弱可訪問)的,並因此可能在任何時刻被回收。
JavaScript 本身具有自動的垃圾回收機制,但在某些情況下還是會發生記憶體洩漏(memory leak)的問題。
當程式不再使用到記憶體,卻沒有將該記憶體清空,就會發生記憶體洩漏。當不被用到的記憶體越來越多,就會出現程式變慢、卡頓、高延遲之類的問題,嚴重的話程式就會崩潰。
一些記憶體洩漏的情況有以下:
使用全域變數,但在用完後沒有設置為 null
JavaScript 的全域變數是由根節點(根據執行環境不同,可能是 window 或 global)引用的,因此它們在應用程式的整個生命週期中都不會被垃圾回收,除非有再設置為 null。在建立太多全域變數或是用全域變數來儲存大量資料......等情況,就會發生記憶體洩漏。
閉包
在函式中回傳另一個函式,function parent(){
var count = 0
return function (){console.log('count'+count)}
}在執行
parent()
之後,因為還有另一個函式在使用count
,所以記憶體不會被清空。Event listeners
使用了 Event listener 但沒有把它移除掉,使用到的 callback 不會被回收記憶體其他情況
使用到
setTimeout
,setInterval
,或是 Cache 沒有定期清理,DOM 元素被移除,多處引用......等,也有可能發生記憶體洩漏。具體例子可以參考這篇文章,Causes of Memory Leaks in JavaScript and How to Avoid Them。
# WeakMap 的用法
const wm = new WeakMap()
wm.delete(key)
// 移除 key 的關聯對象(value)
wm.get(key)
// 取得 key 的關聯對象(value)
wm.has(key)
// 返回 key 是否有關聯對象(value)的 boolean
wm.set(key, value)
// 設置 key 的關聯對象(value)並返回 key/value
# Map VS WeakMap
Map 是 ES6 新加入的資料結構,大體來說跟 Object 很像,但多了一些內建功能。Map 對於物件的引用是強引用,即使物件設定了 null 並且沒有其他引用存在時,也不會被JavaScript 的垃圾回收機制做記憶體回收,造成記憶體洩漏。
相反的,如果是使用 WeakMap,因為 WeakMap 的引用是弱引用,被它引用的物件在設定了 null 並且沒有其他引用存在時,就可以在某一時刻被JavaScript 的垃圾回收機制做記憶體回收。
簡單的重現兩者的差別,這邊直接用開發者工具示範,更嚴謹的方法可以使用 nodeJS 的 node --expose-gc
參數來偵測記憶體的使用狀況。
從圖片結果中可以看到,Map()
引用的物件在設定為 null 之後,也沒有被回收記憶體, WeakMap()
引用的物件在設定為 null 之後,則有被回收記憶體。
# 有和沒有 WeakMap 的差別
如同前面所說,WeakMap 方便用來減少記憶體的洩漏,或者是不想每次都手動(設定為 null)回收記憶體的時候,就可以透過 WeakMap.prototype.delete(key)
或是把被它引用的物件設定為 null 並且沒有其他引用存在時,讓該物件被自動回收。
WeakMap and WeakSets are intended to provide mechanisms for dynamically associating state with an object in a manner that does not “leak” memory resources...
使用場合之一,
let wm = new WeakMap();
wm.set(document.querySelector(".title"), { click: 0 });
document.querySelector(".title").addEventListener(
"click",
function () {
let data = myWeakmap.get(document.querySelector(".title"));
data.click++;
}
);
document.getElementById("logo").remove();
上面例子的 DOM 節點作為 WeakMap 的 key,在這個節點被移除的時候,所對應的 value ({ click: 0 }
)也會被清除。
如果上面的例子沒有使用 WeakMap 的話,並且忘記手動做logoData = null
的動作的話,記憶體就不會被回收,如下。
let data = {click: 0};
document.querySelector(".title").addEventListener(
"click",
function () {
data.click++;
}
);
document.querySelector(".title").remove();
# 總結
WeakMap 適合在映射(mapping) key(object) 和 value 並且這個 object 會在未來某一時間消失的時候使用,讓被使用到的記憶體可以自動回收而不會洩漏。
需要注意的是,WeakMap 弱引用的只是 key(object),而不是 value,value 依然是正常引用。
# 相關資料
你不知道的 WeakMap
Mdn WeakMap
Mdn Map
阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版
从JS中的内存管理说起 —— JS中的弱引用
JavaScript常見的記憶體洩漏
關於作者
This is Umer,烏瑪。