目錄
什麼是 WeakMap
語法
方法
為什麼要用WeakMap 而不是Map
什麼是弱引用
為什麼會出現弱引用
标记清除
引用计数
区别
WeakMap 应用
存储 DOM 节点
数据缓存
部署类中的私有属性
首頁 web前端 Vue.js 什麼是WeakMap? Vue3響應式原始碼中為什麼要用它作為快取區?

什麼是WeakMap? Vue3響應式原始碼中為什麼要用它作為快取區?

Dec 22, 2021 am 10:19 AM
vue.js

這篇文章帶大家了解Vue3中的WeakMap,介紹一下Vue 3 響應式原始碼中為什麼要用 Wea​​kMap 作為「快取區」,希望對大家有幫助。

什麼是WeakMap? Vue3響應式原始碼中為什麼要用它作為快取區?

【相關推薦:《vue.js教學》】

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

WeakMap 物件是一組鍵值對的集合,其中的鍵是 弱引用 的。其鍵必須是 物件,而值可以是任意的。

語法

new WeakMap([iterable])
登入後複製

Iterable 是一個陣列(二元數組)或其他可迭代的且其元素是鍵值對的物件。每個鍵值對會被加到新的 WeakMap 裡。

方法

WeakMap 有四個方法:分別是getsethasdelete,下面我們看一下其大致的用法:

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 持有的是每個鍵物件的弱引用,這表示在沒有其他引用存在時垃圾回收能正確進行。

正是因為這樣的弱引用,WeakMapkey 是不可枚舉的(沒有方法能給出所有的key) 。如果 key 是可枚舉的話,其清單將會受垃圾回收機制的影響,從而得到不確定的結果。因此,如果你想要這種類型物件的 key 值的列表,你應該使用 Map

綜上,我們可以得出以下結論:WeakMap 的鍵所指向的對象,不計入垃圾回收機制

所以,如果你要在物件上新增數據,又不想幹擾垃圾回收機制,就可以使用 WeakMap

看到這裡大家就應該知道了,Vue 3 之所以使用WeakMap 來作為緩衝區就是為了能將不再使用的資料進行正確的垃圾回收

什麼是弱引用

關於「弱引用」,維基百科給出了答案:

在電腦程式設計中,弱引用強引用 相對,是指無法確保其引用的物件不會被垃圾回收器回收的參考。一個物件若只被弱引用所引用,則被認為是不可訪問(或弱可訪問)的,並因此 可能在任何時刻被回收

為什麼會出現弱引用

那麼,為什麼會出現弱引用呢?弱引用除了能解決上述問題之外還能解決什麼問題呢?要回答這些問題,我們首先需要了解 V8 引擎是如何進行垃圾回收的。

JSer 來說,記憶體的管理是自動的、無形的,這一切都歸功於V8 引擎在背後默默地幫我們找到不需要使用的內存並進行清理。

那麼,當我們不再需要某個東西時會發生什麼,V8 引擎又是如何發現並清理它的呢?

现在各大浏览器通常用采用的垃圾回收有两种方法,一种是「引用计数」,另外一种就是「标记清除」。下面我们来看一下:

标记清除

标记清除被称为 mark-and-sweep,它是基于 可达性 来判断对象是否存活的,它会定期执行以下「垃圾回收」步骤:

  • 垃圾收集器找到所有的根,并标记(记住)它们。

  • 然后它遍历并标记来自它们的所有引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。

  • ……如此操作,直到所有可达的(从根部)引用都被访问到。

  • 没有被标记的对象都会被删除。

我们还可以将这个过程想象成从根溢出一个巨大的油漆桶,它流经所有引用并标记所有可到达的对象,然后移除未标记的。

引用计数

引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加 1,每当指向该对象的引用失效时计数器就减 1。当该计数器的值降到 0 就认为对象死亡。

区别

引用计数与基于「可达性」的标记清除的内存管理方式最大的区别就是,前者只需要 局部的信息,而后者需要 全局的信息

在引用计数中每个计数器只记录了其对应对象的局部信息 —— 被引用的次数,而没有(也不需要)一份全局的对象图的生死信息。

由于只维护局部信息,所以不需要扫描全局对象图就可以识别并释放死对象。但也因为缺乏全局对象图信息,所以 无法处理循环引用 的状况。

所以,更高级的引用计数实现会引入 弱引用 的概念来打破某些已知的循环引用。

WeakMap 应用

存储 DOM 节点

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 虽然实现了而且正确输出了 34,但是我们发现可以在外界不通过 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
登入後複製

如上,完美解决~~

更多编程相关知识,请访问:编程入门!!

以上是什麼是WeakMap? Vue3響應式原始碼中為什麼要用它作為快取區?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

深入探討vite是怎麼解析.env檔的 深入探討vite是怎麼解析.env檔的 Jan 24, 2023 am 05:30 AM

使用vue框架開發前端專案時,我們部署的時候都會部署多套環境,往往開發、測試以及線上環境呼叫的介面網域都是不一樣的。如何能做到區分呢?那就是使用環境變數和模式。

圖文詳解如何在Vue專案中整合Ace程式碼編輯器 圖文詳解如何在Vue專案中整合Ace程式碼編輯器 Apr 24, 2023 am 10:52 AM

Ace 是一個用 JavaScript 寫的可嵌入程式碼編輯器。它與 Sublime、Vim 和 TextMate 等原生編輯器的功能和效能相符。它可以很容易地嵌入到任何網頁和 JavaScript 應用程式中。 Ace 被維護為Cloud9 IDE的主要編輯器 ,並且是 Mozilla Skywriter (Bespin) 專案的繼承者。

vue中組件化和模組化有什麼區別 vue中組件化和模組化有什麼區別 Dec 15, 2022 pm 12:54 PM

組件化和模組化的區別:模組化是從程式碼邏輯的角度進行劃分的;方便程式碼分層開發,確保每個每個功能模組的職能一致。元件化是從UI介面的角度進行規劃;前端的元件化,方便UI元件的重複使用。

深入聊聊vue3中的reactive() 深入聊聊vue3中的reactive() Jan 06, 2023 pm 09:21 PM

前言:在vue3的開發中,reactive是提供實現響應式資料的方法。日常開發這個是使用頻率很高的api。這篇文章筆者就來探索其內部運作機制。

探討如何在Vue3中撰寫單元測試 探討如何在Vue3中撰寫單元測試 Apr 25, 2023 pm 07:41 PM

在當今前端開發中,Vue.js 已經成為了一個非常流行的框架。隨著 Vue.js 的不斷發展,單元測試變得越來越重要。今天,我們將探討如何在 Vue.js 3 中編寫單元測試,並提供一些最佳實踐和常見的問題及解決方案。

淺析Vue3動態組件怎麼進行異常處理 淺析Vue3動態組件怎麼進行異常處理 Dec 02, 2022 pm 09:11 PM

Vue3動態元件怎麼進行異常處理?以下這篇文章帶大家聊聊Vue3 動態元件異常處理的方法,希望對大家有幫助!

Vue中JSX語法和模板語法的簡單比較(優劣勢分析) Vue中JSX語法和模板語法的簡單比較(優劣勢分析) Mar 23, 2023 pm 07:53 PM

在Vue.js中,開發人員可以使用兩種不同的語法來建立使用者介面:JSX語法和範本語法。這兩種文法各有優劣,以下就來探討它們的差異和優劣勢。

淺析vue怎麼實現檔案切片上傳 淺析vue怎麼實現檔案切片上傳 Mar 24, 2023 pm 07:40 PM

在實際開發專案過程中有時候需要上傳比較大的文件,然後呢,上傳的時候相對來說就會慢一些,so,後台可能會要求前端進行文件切片上傳,很簡單哈,就是把比如說1個G的檔案流切割成若干個小的檔案流,然後分別請求介面傳遞這個小的檔案流。

See all articles