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