在學習緩存函數時,最後提到了WeakMap方式緩存(對入參類型爲對象作緩存,而且當對象在WeakMap中的key沒有引用時方便瀏覽器垃圾回收)html
If our parameter were an object (rather than a string, like it is above), we could use WeakMap instead of Map in modern browsers. The benefit of WeakMap is that it would automatically 「clean up」 the entries when our object key is no longer accessible.
並且JavaScript既然已經有了Map類型的數據結構,爲何還有一種叫作WeakMap類型的數據結構呢?它和垃圾回收有什麼關係?react
WeakMap很早以前就遇到過,可是沒有系統學習過,今天就來對它一探究竟。git
初識WeakMapgithub
弱引用的意義:若是是做爲key的對象沒有任何地方引用它的話,垃圾收集器(GC)會將其標記爲目標而且進行垃圾回收。數組
key:必須是任意object類型(對象、數組、Map、WeakMap等等)
value:any(任意類型,因此也包括undefined,null)瀏覽器
WeakMap的key是不可枚舉的,而Map是可枚舉的。
不可枚舉就意味着獲取不到WeakMap的key列表。緩存
設計爲不可枚舉的緣由是由於:若是枚舉WeakMap的key,那就須要依賴垃圾回收器(GC)的狀態,從而引入不肯定性。數據結構
map API在js中能夠經過共享4個API(get,set,has,delete)的兩個數組來實現:一個存儲key,一個存儲value。在這個map上設置元素同步推入一個key和一個value到數組尾部。做爲結果,key和value的索引會和兩個數組綁定起來。從map獲取一個值得話,會遍歷全部key去找到一個匹配的,而後使用這個匹配到的index從values數組中查詢到對應的值。ide
這樣實現的話會有2個主要的弊端:函數
相比之下,原生的WeakMap
會保持對key的「弱」引用。原生的WeakMap
不會阻止垃圾回收,最終會移除對key對象的引用。「弱」引用一樣可讓value很好地垃圾回收。WeakMap特別適用於key映射的信息只有不被垃圾回收時纔有價值的場景,換句話說就是WeakMap適用於動態垃圾回收key的場景。
由於引用是弱的,因此WeakMap的鍵是不能枚舉的。沒有方法去獲取key的列表。若是枚舉WeakMap的key,那就須要依賴垃圾回收器(GC)的狀態,從而引入不肯定性。若是必需要有key的話,應該去使用Map
。
new WeakMap() new WeakMap(iterable)
其中iterable是數組或者任意能夠迭代的對象,須要擁有key-value對(通常是一個二維數組)。null會被當作undefined。
const iterable = [ [{foo:1}, 1], [[1,2,3], 2], [window, 3] ] const iwm = new WeakMap(iterable) // WeakMap {{…} => 1, Window => 3, Array(3) => 2}
WeakMap.prototype.delete(key)
刪除key關聯的任意值。刪除後WeakMap.prototype.has(key)
返回false。
WeakMap.prototype.get(key)
返回與key關聯的值,假設不存在關聯值得話返回undefined。
WeakMap.prototype.has(key)
返回key在WeakMap上是否存在的結果。
WeakMap.prototype.set(key, value)
在WeakMap對象上爲對應key設置指定的value。而且返回WeakMap對象
const wm1 = new WeakMap(), wm2 = new WeakMap(), wm3 = new WeakMap(); const o1 = {}, o2 = function() {}, o3 = window; wm1.set(o1, 37); wm1.set(o2, 'azerty'); wm2.set(o1, o2); // WeakMap的值能夠是任意類型,包括object和function wm2.set(o3, undefined); wm2.set(wm1, wm2); // key和value能夠是任意對象。包括WeakMap! wm1.get(o2); // "azerty" wm2.get(o2); // undefined, wm2上沒有o2這個key wm2.get(o3); // undefined, 由於這是設置的值 wm1.has(o2); // true wm2.has(o2); // false wm2.has(o3); // true (即便value是undefined) wm3.set(o1, 37); wm3.get(o1); // 37 wm1.has(o1); // true wm1.delete(o1); wm1.has(o1); // false
實例和原型鏈上的數據和方法是公開的,因此能夠經過WeakMap類型的私有變量去隱藏實現細節。
const privates = new WeakMap(); function Public() { const me = { // Private data goes here }; privates.set(this, me); } Public.prototype.method = function () { const me = privates.get(this); // Do stuff with private data in `me`... }; module.exports = Public;
class ClearableWeakMap { constructor(init) { this._wm = new WeakMap(init); } clear() { this._wm = new WeakMap(); } delete(k) { return this._wm.delete(k); } get(k) { return this._wm.get(k); } has(k) { return this._wm.has(k); } set(k, v) { this._wm.set(k, v); return this; } }
const key1 = {foo:1}; const key2 = [1,2,3]; const key3 = window; const cwm = new ClearableWeakMap([ [key1, 1], [key2, 2], [key3, 3] ]) cwm.has(key1) // true console.log(cwm);// ClearableWeakMap {_wm: WeakMap {Window => 3, {…} => 1, Array(3) => 2}} cwm.clear(); // 垃圾回收當前WeakMap,而且聲稱新的空WeakMap cwm.has(key1) // false console.log(cwm);// ClearableWeakMap {_wm: WeakMap {}}
實現緩存函數的方式有不少種,好比單次緩存,Map式全量緩存,LRU最近最少緩存等等。
那麼爲何還須要WeakMap式的緩存函數呢?這是由於入參爲對象類型的緩存且方便瀏覽器垃圾回收。
function memoizeWeakMap(fn) { const wm = new WeakMap(); return function (arg) { if (wm.has(arg)) { return wm.get(arg); } const cachedArg = arg; const cachedResult = fn(arg); wm.set(cachedArg, cachedResult) console.log('weakmap object', wm) return cachedResult; }; } let testFn = (bar) => {return Object.prototype.toString.call(bar)}; // 這裏須要改造一下,改造完返回傳入對象的類型 let memoizeWeakMapFn = memoizeWeakMap(testFn); memoizeWeakMapFn(document) // weakmap對document生成緩存 memoizeWeakMapFn([1,2,3]) // weakmap對[1,2,3]生成緩存 memoizeWeakMapFn(function(){}) // weakmap對function(){}生成緩存 memoizeWeakMapFn(new WeakMap()) // weakmap對WeakMap實例生成緩存 memoizeWeakMapFn(new Map()) // weakmap對Map實例生成緩存 memoizeWeakMapFn(new Set()) // weakmap對Set實例生成緩存 WeakMap: 0: {Array(3) => "[object Array]"} 1: {function(){} => "[object Function]"} 2: {WeakMap => "[object WeakMap]"} 3: {Map(0) => "[object Map]"} 4: {#document => "[object HTMLDocument]"} 5: {Set(0) => "[object Set]"}
// 忽略部分代碼同上 setTimeout(()=>{ memoizeWeakMapFn(document) },5000)
此時有時最後一次weakmap的打印結果以下:
WeakMap: 0: {#document => "[object HTMLDocument]"}
由於打印時垃圾回收可能並無執行完成,雖然會帶來不肯定性,可是能夠肯定的是,假設對象沒有再被引用,WeakMap中的key會被瀏覽器自動垃圾回收掉。
這是由於[1,2,3], function(){},new WeakMap(),new Map(),new Set()在後面都沒有再繼續引用了,並且由於它們做爲了WeakMap的key,因此會被瀏覽器自動垃圾回收掉。
保持一個變量對它的引用。
let memoizeWeakMapFn = memoizeWeakMap(testFn); let retainArray = [1,2,3]; // 保持引用避免被垃圾回收 let retainMap = new Map(); // 保持引用避免被垃圾回收 memoizeWeakMapFn(document) // weakmap對document生成緩存 memoizeWeakMapFn(retainArray) // weakmap對[1,2,3]生成緩存 memoizeWeakMapFn(function(){}) // weakmap對function(){}生成緩存 memoizeWeakMapFn(new WeakMap()) // weakmap對WeakMap實例生成緩存 memoizeWeakMapFn(retainMap) // weakmap對Map實例生成緩存 memoizeWeakMapFn(new Set()) // weakmap對Set實例生成緩存 setTimeout(()=>{ memoizeWeakMapFn(document) },5000)
此時打印結果爲:
WeakMap: 0: {#document => "[object HTMLDocument]"} 1: {Map(0) => "[object Map]"} 2: {Array(3) => "[object Array]"}
這是由於[1,2,3], new Map()被變量retainArray和retainMap持續引用着,因此不會被垃圾回收。而function(){},new WeakMap(),new Set()都沒有再繼續引用了,並且由於它們做爲了WeakMap的key,因此會被瀏覽器自動垃圾回收掉。
能夠藉助Chrome DevTools的memory面板工具,有一個手動觸發垃圾回收的按鈕。
// ... setTimeout(()=>{ memoizeWeakMapFn(document) },5000)
好比在上面的例子中,設置了一個5秒的延時:只要代碼運行後的5秒內,去手動觸發「垃圾回收按鈕」,就能夠很精確地看到WeakMap的key被垃圾回收了。
固然5秒這個時間是能夠人爲調整的,保證本身能在setTimeout內的代碼運行前觸發對WeakMap的垃圾回收便可,能夠適當調大。
參考資料:
https://developer.mozilla.org...
https://developer.mozilla.org...
https://fitzgeraldnick.com/20...
https://whatthefuck.is/memoiz...
https://github.com/reactjs/re...