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