您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue3中怎么用WeakMap作為緩存區(qū)”,在日常操作中,相信很多人在Vue3中怎么用WeakMap作為緩存區(qū)問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Vue3中怎么用WeakMap作為緩存區(qū)”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
在讀 Vue 3 響應(yīng)式原理部分代碼的過(guò)程中看到其在進(jìn)行響應(yīng)式處理的時(shí)候,為每個(gè)對(duì)象使用 WeakMap
創(chuàng)建了一個(gè)「緩存區(qū)」,代碼如下:
// 注意下面這句代碼! const reactiveMap = new WeakMap(); // 核心進(jìn)行劫持的方法 處理 get 和 set 的邏輯 const mutableHandlers = { get, set } function reactive(target: object) { return createReactiveObject(target, mutableHandlers, reactiveMap); } /** * @description 創(chuàng)建響應(yīng)式對(duì)象 * @param {Object} target 需要被代理的目標(biāo)對(duì)象 * @param {Function} baseHandlers 針對(duì)每種方式對(duì)應(yīng)的不同處理函數(shù) * @param {Object} proxyMap WeakMap 對(duì)象 */ function createReactiveObject(target, baseHandlers, proxyMap) { // 檢測(cè) target 是不是對(duì)象,不是對(duì)象直接返回,不進(jìn)行代理 if (!isObject(target)) { return target } const existsProxy = proxyMap.get(target); // 如果該對(duì)象已經(jīng)被代理過(guò)了,則直接返回,不進(jìn)行重復(fù)代理 if (existsProxy) { return existsProxy } // 未被代理過(guò),則創(chuàng)建代理對(duì)象 const proxy = new Proxy(target,baseHandlers); // 緩存,避免重復(fù)代理,即避免 reactive(reactive(Object)) 的情況出現(xiàn) proxyMap.set(target,proxy); return proxy }
從上面的代碼可以看出,WeakMap
緩存區(qū)的作用就是用來(lái)防止對(duì)象被重復(fù)代理。
為什么 Vue 3 使用 WeakMap
來(lái)緩存代理對(duì)象?為什么不使用其他的方式來(lái)進(jìn)行緩存,比如說(shuō) Map
?
WeakMap
對(duì)象是一組鍵值對(duì)的集合,其中的鍵是 弱引用 的。其鍵必須是 對(duì)象,而值可以是任意的。
new WeakMap([iterable])
Iterable
是一個(gè)數(shù)組(二元數(shù)組)或者其他可迭代的且其元素是鍵值對(duì)的對(duì)象。每個(gè)鍵值對(duì)會(huì)被加到新的 WeakMap
里。
WeakMap
有四個(gè)方法:分別是 get
、set
、has
、delete
,下面我們看一下其大致的用法:
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); // value 可以是任意值,包括一個(gè)對(duì)象或一個(gè)函數(shù) wm2.set(o3, undefined); wm2.set(wm1, wm2); // 鍵和值可以是任意對(duì)象,甚至另外一個(gè) WeakMap 對(duì)象 wm1.get(o2); // "azerty" wm2.get(o2); // undefined,wm2 中沒(méi)有 o2 這個(gè)鍵 wm2.get(o3); // undefined,值就是 undefined wm1.has(o2); // true wm2.has(o2); // false wm2.has(o3); // true (即使值是 undefined) wm3.set(o1, 37); wm3.get(o1); // 37 wm1.has(o1); // true wm1.delete(o1); wm1.has(o1); // false
WeakMap
而不是 Map
在 JavaScript 里,map
API 可以通過(guò)四個(gè) API 方法共用兩個(gè)數(shù)組(一個(gè)存放鍵,一個(gè)存放值)來(lái)實(shí)現(xiàn)。這樣在給這種 map
設(shè)置值時(shí)會(huì)同時(shí)將鍵和值添加到這兩個(gè)數(shù)組的末尾。從而使得鍵和值的索引在兩個(gè)數(shù)組中相對(duì)應(yīng)。當(dāng)從該 map
取值的時(shí)候,需要遍歷所有的鍵,然后使用索引從存儲(chǔ)值的數(shù)組中檢索出相應(yīng)的值。
但這樣的實(shí)現(xiàn)會(huì)有兩個(gè)很大的缺點(diǎn),首先賦值和搜索操作都是 O(n)
的時(shí)間復(fù)雜度(n
是鍵值對(duì)的個(gè)數(shù)),因?yàn)檫@兩個(gè)操作都需要遍歷整個(gè)數(shù)組來(lái)進(jìn)行匹配。
另外一個(gè)缺點(diǎn)是可能會(huì)導(dǎo)致 內(nèi)存泄漏,因?yàn)閿?shù)組會(huì)一直引用著每個(gè)鍵和值。這種引用使得 垃圾回收算法不能回收處理他們,即使沒(méi)有其他任何引用存在了。
let jser = { name: "dachui" }; let array = [ jser ]; jser = null; // 覆蓋引用
上面這段代碼,我們把一個(gè)對(duì)象放入到數(shù)組中,那么只要這個(gè)數(shù)組存在,那么這個(gè)對(duì)象也就存在,即使沒(méi)有其他對(duì)該對(duì)象的引用。
let jser = { name: "dachui" }; let map = new Map(); map.set(jser, ""); jser = null; // 覆蓋引用
類似的,如果我們使用對(duì)象作為常規(guī) Map
的鍵,那么當(dāng) Map
存在時(shí),該對(duì)象也將存在。它會(huì)占用內(nèi)存,并且不會(huì)被垃圾回收機(jī)制回收。
相比之下,原生的 WeakMap
持有的是每個(gè)鍵對(duì)象的 弱引用,這意味著在沒(méi)有其他引用存在時(shí)垃圾回收能正確進(jìn)行。
正是由于這樣的弱引用,WeakMap
的 key
是不可枚舉的 (沒(méi)有方法能給出所有的 key
)。如果 key
是可枚舉的話,其列表將會(huì)受垃圾回收機(jī)制的影響,從而得到不確定的結(jié)果。因此,如果你想要這種類型對(duì)象的 key
值的列表,你應(yīng)該使用 Map
。
綜上,我們可以得出以下結(jié)論:WeakMap 的鍵所指向的對(duì)象,不計(jì)入垃圾回收機(jī)制。
所以,如果你要往對(duì)象上添加數(shù)據(jù),又不想干擾垃圾回收機(jī)制,就可以使用 WeakMap
。
看到這里大家就應(yīng)該知道了,Vue 3 之所以使用 WeakMap
來(lái)作為緩沖區(qū)就是為了能將 不再使用的數(shù)據(jù)進(jìn)行正確的垃圾回收。
關(guān)于「弱引用」,維基百科給出了答案:
在計(jì)算機(jī)程序設(shè)計(jì)中,弱引用 與 強(qiáng)引用 相對(duì),是指不能確保其引用的對(duì)象不會(huì)被垃圾回收器回收的引用。一個(gè)對(duì)象若只被弱引用所引用,則被認(rèn)為是不可訪問(wèn)(或弱可訪問(wèn))的,并因此 可能在任何時(shí)刻被回收。
那么,為什么會(huì)出現(xiàn)弱引用呢?弱引用除了能解決上述問(wèn)題之外還能解決什么問(wèn)題呢?要想回答這些問(wèn)題,我們首先需要了解一下 V8
引擎是如何進(jìn)行垃圾回收的。
對(duì)于 JSer
來(lái)說(shuō),內(nèi)存的管理是自動(dòng)的、無(wú)形的,這一切都?xì)w功于 V8
引擎在背后默默地幫我們找到不需要使用的內(nèi)存并進(jìn)行清理。
那么,當(dāng)我們不再需要某個(gè)東西時(shí)會(huì)發(fā)生什么,V8
引擎又是如何發(fā)現(xiàn)并清理它的呢?
現(xiàn)在各大瀏覽器通常用采用的垃圾回收有兩種方法,一種是「引用計(jì)數(shù)」,另外一種就是「標(biāo)記清除」。下面我們來(lái)看一下:
標(biāo)記清除被稱為 mark-and-sweep
,它是基于 可達(dá)性 來(lái)判斷對(duì)象是否存活的,它會(huì)定期執(zhí)行以下「垃圾回收」步驟:
垃圾收集器找到所有的根,并標(biāo)記(記住)它們。
然后它遍歷并標(biāo)記來(lái)自它們的所有引用。所有被遍歷到的對(duì)象都會(huì)被記住,以免將來(lái)再次遍歷到同一個(gè)對(duì)象。
……如此操作,直到所有可達(dá)的(從根部)引用都被訪問(wèn)到。
沒(méi)有被標(biāo)記的對(duì)象都會(huì)被刪除。
我們還可以將這個(gè)過(guò)程想象成從根溢出一個(gè)巨大的油漆桶,它流經(jīng)所有引用并標(biāo)記所有可到達(dá)的對(duì)象,然后移除未標(biāo)記的。
引用計(jì)數(shù)方式最基本的形態(tài)就是讓每個(gè)被管理的對(duì)象與一個(gè)引用計(jì)數(shù)器關(guān)聯(lián)在一起,該計(jì)數(shù)器記錄著該對(duì)象當(dāng)前被引用的次數(shù),每當(dāng)創(chuàng)建一個(gè)新的引用指向該對(duì)象時(shí)其計(jì)數(shù)器就加 1,每當(dāng)指向該對(duì)象的引用失效時(shí)計(jì)數(shù)器就減 1。當(dāng)該計(jì)數(shù)器的值降到 0 就認(rèn)為對(duì)象死亡。
引用計(jì)數(shù)與基于「可達(dá)性」的標(biāo)記清除的內(nèi)存管理方式最大的區(qū)別就是,前者只需要 局部的信息,而后者需要 全局的信息。
在引用計(jì)數(shù)中每個(gè)計(jì)數(shù)器只記錄了其對(duì)應(yīng)對(duì)象的局部信息 —— 被引用的次數(shù),而沒(méi)有(也不需要)一份全局的對(duì)象圖的生死信息。
由于只維護(hù)局部信息,所以不需要掃描全局對(duì)象圖就可以識(shí)別并釋放死對(duì)象。但也因?yàn)槿狈θ謱?duì)象圖信息,所以 無(wú)法處理循環(huán)引用 的狀況。
所以,更高級(jí)的引用計(jì)數(shù)實(shí)現(xiàn)會(huì)引入 弱引用 的概念來(lái)打破某些已知的循環(huán)引用。
WeakMap
應(yīng)用的典型場(chǎng)合就是以 DOM
節(jié)點(diǎn)作為鍵名。下面是一個(gè)例子。
const myWeakmap = newWeakMap(); myWeakmap.set( document.getElementById('logo'), { timesClicked: 0 }, ); document.getElementById('logo').addEventListener('click', () => { const logoData = myWeakmap.get(document.getElementById('logo')); logoData.timesClicked++; }, false);
上面代碼中,document.getElementById('logo')
是一個(gè) DOM
節(jié)點(diǎn),每當(dāng)發(fā)生 click
事件,就更新一下狀態(tài)。我們將這個(gè)狀態(tài)作為值放在 WeakMap
里,對(duì)應(yīng)的鍵就是這個(gè)節(jié)點(diǎn)對(duì)象。一旦這個(gè) DOM
節(jié)點(diǎn)刪除,該狀態(tài)就會(huì)自動(dòng)消失,不存在內(nèi)存泄漏風(fēng)險(xiǎn)。
謎底就在謎面上,文章一開頭我們提出的問(wèn)題就是這里的答案。Vue 3 在實(shí)現(xiàn)響應(yīng)式原理的時(shí)候就是使用了 WeakMap
來(lái)作為響應(yīng)式對(duì)象的「緩存區(qū)」。
關(guān)于這一點(diǎn)用法也很簡(jiǎn)單,當(dāng)我們需要關(guān)聯(lián)對(duì)象和數(shù)據(jù),比如在不修改原有對(duì)象的情況下儲(chǔ)存某些屬性或者根據(jù)對(duì)象儲(chǔ)存一些計(jì)算的值等,而又不想手動(dòng)去管理這些內(nèi)存問(wèn)題的時(shí)候就可以使用 WeakMap
。
WeakMap
的另一個(gè)用處是部署類中的私有屬性。
值得一提的是,TypeScript 中已經(jīng)實(shí)現(xiàn)的 private
私有屬性原理就是利用 WeakMap
。
私有屬性應(yīng)該是不能被外界訪問(wèn)到,不能被多個(gè)實(shí)例共享,JavaScript 中約定俗成地使用下劃線來(lái)標(biāo)記私有屬性和方法,一定程度來(lái)說(shuō)是不靠譜的。
下面我們用三種方法來(lái)實(shí)現(xiàn):
版本一:閉包
const testFn = (function () { let data; class Test { constructor(val) { data = val } getData() { return data; } } return Test; })(); let test1 = new testFn(3); let test2 = new testFn(4); console.log(test1.getData()); // 4 console.log(test2.getData()); // 4
可以看到最后都輸出 4
,多實(shí)例共享私有屬性了,所以版本一不符合。
版本二:Symbol
const testFn = (function () { let data = Symbol('data') class Test { constructor(val) { this[data] = val } getData() { return this[data] } } return Test; })(); let test1 = new testFn(3); let test2 = new testFn(4); console.log(test1.getData()); // 3 console.log(test2.getData()); // 4 console.log(test1[Object.getOwnPropertySymbols(test1)[0]]); // 3 console.log(test2[Object.getOwnPropertySymbols(test2)[0]]); // 4
使用 Symbol
雖然實(shí)現(xiàn)了而且正確輸出了 3
、4
,但是我們發(fā)現(xiàn)可以在外界不通過(guò) getData
方法直接拿到私有屬性,所以這種方法也不滿足我們的要求。
版本三:WeakMap
const testFn = (function () { let data = new WeakMap() class Test { constructor(val) { data.set(this, val) } getData() { return data.get(this) } } return Test; })(); let test1 = new testFn(3); let test2 = new testFn(4); console.log(test1.getData()); // 3 console.log(test2.getData()); // 4
到此,關(guān)于“Vue3中怎么用WeakMap作為緩存區(qū)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。