溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue3中怎么用WeakMap作為緩存區(qū)

發(fā)布時(shí)間:2021-12-22 10:37:11 來(lái)源:億速云 閱讀:194 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“Vue3中怎么用WeakMap作為緩存區(qū)”,在日常操作中,相信很多人在Vue3中怎么用WeakMap作為緩存區(qū)問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Vue3中怎么用WeakMap作為緩存區(qū)”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

Vue3中怎么用WeakMap作為緩存區(qū)

在讀 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

WeakMap 對(duì)象是一組鍵值對(duì)的集合,其中的鍵是 弱引用 的。其鍵必須是 對(duì)象,而值可以是任意的。

語(yǔ)法

new WeakMap([iterable])

Iterable 是一個(gè)數(shù)組(二元數(shù)組)或者其他可迭代的且其元素是鍵值對(duì)的對(duì)象。每個(gè)鍵值對(duì)會(huì)被加到新的 WeakMap 里。

方法

WeakMap 有四個(gè)方法:分別是 getset、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)行。

正是由于這樣的弱引用,WeakMapkey 是不可枚舉的 (沒(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)弱引用

那么,為什么會(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)記清除

標(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ù)

引用計(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ì)象死亡。

區(qū)別

引用計(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ǔ) DOM 節(jié)點(diǎ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)。

數(shù)據(jù)緩存

謎底就在謎面上,文章一開頭我們提出的問(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í)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI