ES6+ WeakMap
1. 前言
前面我們已經學了 Set 和對應的 WeakSet,Map 對應也有 WeakMap,在學習 WeakSet 時我們已經接觸到弱引的相關知識,本節我們將結合 WeakMap 深入的理解弱引用的相關問題。
由前面學到的 Set 和 WeakSet 具有很多相似的地方,比如它們存放的都是獨一無二的元素。所以,對 Map 和 WeakMap 也可以進行類比,WeakMap 中也存放的是鍵值對。不同的是 WeakMap 的 key 只能是對象,值可以是任意類型的,和 WeakSet 一樣 WeakMap 對 key 的引用是弱引用。
2. WeakMap 基本用法
WeakMap 像 Map 一樣可以接受一個二維數組進行初始化。
var wm = new WeakMap([
[{name: 'imooc'}, 'imooc'],
[{name: 'lesson'}, 'ES6 Wiki']
])
console.log(wm)
上面的代碼打印結果如下:
從打印的結果可以大概了解 WeakMap 的存儲方式,WeakMap 的實例本來就是一個對象。
WeakMap 只提供了四個方法用于操作數據。
方法名 | 描述 |
---|---|
set | 接收鍵值對,向 WeakMap 實例中添加元素 |
get | 傳入指定的 key 獲取 WeakMap 實例上的值 |
has | 傳入指定的 key 查找在 WeakMap 實例中是否存在 |
delete | 傳入指定的 key 刪除 WeakMap 實例中對應的值 |
看如下實例:
var wm1 = new WeakMap();
var wm2 = new WeakMap();
var wm3 = new WeakMap();
var o1 = {name: 'imooc'};
var o2 = function(){};
var o3 = window;
// 使用 set 方法添加元素,value 可以是任意值,包括對象、函數甚至另外一個WeakMap對象
wm1.set(o1, 'ES6 Wiki');
wm1.set(o2, 10);
wm2.set(o1, o2);
wm2.set(o3, null);
wm2.set(wm1, wm2);
wm1.get(o2); // 10
wm2.get(o2); // undefined,wm2 中沒有 o2 這個鍵
wm2.get(o3); // null
wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是null)
wm3.set(o1, 'lesson is ES6 Wiki!');
wm3.get(o1); // lesson is ES6 Wiki!
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
上面的實例基本涵蓋了 WeakMap 四種方法的基本使用情況,上面也提到了 WeakMap 的 key 只能是對象類型的,如果 WeakMap 的 key 是基本類型數據時就會報錯。
var wm = new WeakMap();
wm.set('lesson', 'ES6 Wiki');
// Uncaught TypeError: Invalid錯誤value used as weak map key
上面代碼中在設置 wm 值時,報錯了。從報錯類型知道是一個類型錯誤,弱引用映射的鍵是無效的。
3. WeakMap 使用場景
上節我們學習了 Map 的使用,在 JavaScript 中對對象的引用都是強保留的,這意味著只要持有該對象的引用,垃圾回收機制就不會回收該對象。
var obj = {a: 10, b: 88};
上面是一個字面量對象,只要我們訪問 obj 對象,或者任何地方有引用該對象,這個對象就不會被垃圾回收。而在 ES6 之前 JavaScript 中沒有弱引用概念,弱引用的本質上就是不會影響垃圾回收機制。其實,WeakMap 并不是真正意義上的弱引用,只要鍵仍然存在,它就強引用其上的內容。WeakMap 僅在鍵被垃圾回收之后,才弱引用它的內容,所以也不用太糾結其中的弱。
在官方上對為什么使用 WeakMap 做了描述,Map 在存儲值是有順序的,這種順序是通過二維數組的形式來完成的。我們知道 Map 在初始化時接受一個數組,數組中的每一項也是一個數組,這個數組中包含兩個值,一個存放的是鍵,一個存放的是值。新添加的值會添加到數組的末尾,從而使得鍵值具有索引的含義。在取值時就需要進行遍歷,通過索引取出對應的值。
但是這樣存在兩個很大的缺陷:
- 賦值和搜索的時間復雜度都是 O (n) (n 是鍵值對的個數),因為這兩個操作都是要遍歷整個數組才能完成的;
- 可能會導致內存泄漏,因為數組會一直引用每個鍵和值。這種引用使得垃圾回收算法不能回收處理它們,即使沒有任何引用存在。
相比之下,原生的 WeakMap 持有的是 “弱引用”,這意味著它不會影響垃圾回收。WeakMap 中的 key 只有在鍵值存在的情況才會引用,而且只是一個讀取操作,并不會對引用的值產生影響。也正因為這樣的弱引用關系,導致 WeakMap 中的 key 是不可枚舉的,假設 key 是可枚舉的,就會對該值產生引用關系,影響垃圾回收。
如果只是單純地向對象上添加值用于檢查某些邏輯判斷,又不想影響垃圾回收機制,這個時候就可以使用 WeakMap。這里說一點,在一些框架中已經使用了像 WeakMap 和 WeakSet 這樣的數據結構,其中 Vue3 就引入了這樣的新數據進行一些必要的邏輯判斷,有興趣的可以去扒扒 Vue3 的源碼研究研究。
4. 總結
本節主要介紹了 WeakMap 的使用和應用場景,這里要說明的一點是:WeakMap 不算真正意義上的弱引用方式,只要鍵仍然存在,它就強引用其上的內容。最新的 ES 方案提出了 WeakRef 的 API 作為真正的弱引用方式,現在還處于不穩定期間,也還存在一些問題,如果有興趣的可以研究一下。最后,在 WeakMap 的使用上,大多數都是用來進行一些必要的邏輯判斷的。在 WeakMap 實例上添加一個對已知對象的引用,從而在需要使用時,對該對象進行必要的邏輯判斷。