1. 程式人生 > 程式設計 >vue3為什麼要用proxy替代defineProperty

vue3為什麼要用proxy替代defineProperty

在這之前,我們得先了解下vue的核心理念mutable

不管是vue2還是vue3,在實現的過程中,核心概念一直保持穩定,以可變資料來源為核心的理念,來實現整個UI變動更新

用最簡單的講法就是:初始化資料生成了頁面,直接修改源資料觸發更新,頁面重新渲染

關注vue的人都知道,vue3裡面使用了proxy替換了defineProperty,

在使用vue2的時候,我們經常會碰到一個問題,新增新的物件屬性obj.a = 1會無法被vue2劫持,必須使用vue2提供的$set方法來進行更新

這個的原因想必大家也都清楚,因為defineProperty只能對當前物件的其中一個屬性進行劫持

const a = {
  b: 1,};
Object.defineProperty(a,'b',{
  set: function() {},get: function() {},});

當我們給a物件新增一個屬性的時候,當前新增的屬性並沒有被defineProperty劫持,雖然在對應的物件上依舊成功的生成了一個新的屬性,但是我們知道,vue2是通過defineProperty的setter與getter進行資料劫持的,既然新增的資料並沒有被劫持,所以無論怎麼更新,頁面依舊不會重新渲染

而在vue3中,使用proxy來進行資料代理就完全沒有這個顧慮了

const p = new Proxy({
  a: 1,b: 2,},{
  get: function(obj,value) {
    console.log('get',obj,value);
    return Reflect.get(obj,value);
  },set: function(obj,prop,value) {
    console.log('set',value);
    return Reflect.set(obj,})

proxy對於資料的代理,是能夠響應新增的屬性,當新增一個屬性的時候,可以響應到get中,對當前物件進行代理

vue3是如何通過proxy代理的

首先可以看下vue3新增的幾個主要apiref,reactive,effect,computed

ref和reactive
const normal = ref(0);
const state = reactive({
  a: 1,})

vue3中對vue2的相容處理也是使用了reactive,即instance.data = reactive(data),將整個data屬性使用reactive進行代理

我們知道,vue2中的data就是使用Object.definePerproty進行資料劫持的, 那麼在reactive中,他是如何使用proxy進行資料代理的,來相容老的書寫方式與新的compositionApi

ps: 由於在reactive裡面也只是通過proxy對傳入的資料校驗和代理,最主要的還是set和get,所以我們還是直接上壘吧,畢竟心急吃得了熱豆腐

get

可以分析一下,vue2也好,vue3也罷,針對於資料的獲取所做的事情主要內容不會有什麼區別

  1. 獲取當前需要的key的資料
  2. 依賴採集

但是,針對於vue3使用proxy的特性,在這邊額外做了一部分的相容

  1. 如果獲取的資料是一個物件,則會對物件再使用reactive進行一次資料的代理
  2. 如果是shallow型別的資料代理, 則直接返回當前獲取到的資料

effect依賴採集

vue除去正常的data的資料代理以外,還有對應的computed和watch,而在vue3中直接使用了watchEffect和computed方法能夠直接生成對應的內容

他們的資料更新和依賴處理都是依託於當前data資料上的get進行依賴的收集

掐頭去尾的來看最核心的程式碼

// targetMap當前所有代理的資料的一個Map集合
// depsMap當前代理的資料的每一個Key所對應的Map集合
// dep當前代理的資料中的key的對應依賴
// activeEffect當前由effect或者computed生成的資料

let depsMap = targetMap.get(target)
if (!depsMap) {
  targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
  depsMap.set(key,(dep = new Set()))
}
if (!dep.has(activeEffect)) {
  dep.add(activeEffect)
  activeEffect.deps.push(dep)
}

單純從這段程式碼出發去解讀的話,可能會有一定的困難,換個角度,從vue3的整體使用情況出發,返回來解讀這段程式碼

setup() {
  const b = reactive({
    c: 1,});
  // effect是vue中的reactivity包直接返回出來的方法
  const a = effect(() => {
    return b.c;
  })
}

首先,在effect中使用了前面通過reactive定義的b,從表面現象出發的話,我們能知道,當b.c發生變化的時候,a也會同步發生變化

這個變化的原因就是上述原始碼中的activeEffect,當建立的effect被呼叫的時候,會將activeEffect設定為自身,並執行相應的回撥函式,函式的呼叫會觸發到各自使用到的資料的getter,將對應的effect依賴注入到每個使用的資料上

至於為什麼會設定這麼複雜的一個屬性的依賴獲取,是因為使用proxy的原因,proxy代理了一整個物件,就不能像vue2使用Obect.defineProperty直接在getter裡面就當前的欄位進行一個依賴繫結,所以在vue3中是直接將整個物件作為一個Map,每個Map的key都是對應的屬性,而value則是所有依賴當前屬性的物件

set

同get,依舊保持著原先的思路跟模式

  1. 設定當前資料
  2. 釋出已訂閱的資料(觸發依賴更新)

在vue3中,還是有部分區別的,畢竟是單獨拉出去的一個庫

  1. 如果直接呼叫effect,當檢測的資料發生變化的時候會直接修改
  2. 如果呼叫watch或者watchEffect,則會走vue自身的排程方案

所以,如果想當前的資料直接可以更新的話, 可以優先使用effect,他會比watchEffect的更新速度快一點,劣勢是可能很多東西得自己寫 =,=

至於怎麼實現的其實就很簡單了

  1. 獲取當前更新的資料的受依賴項
  2. 分組進入等待執行的Set中
  3. 執行

但是裡面有一個特殊的處理,針對於陣列的length屬性,這個屬性是有一定區別的,接下來具體講講在vue3中的陣列操作

陣列

在vue2中的,針對陣列是多做了一層處理,代理了陣列的基本方法,這是因為使用Object.defineProperty在陣列上面天然存在劣勢

具體原因在vue的文件中寫的非常清楚了,這裡就不詳細敘述了

文件地址

而在vue3中使用proxy就完美的解決了這個問題,只是因為proxy能夠監聽陣列的變化,做個測試

const a = new Proxy([1,2],prop) {
    console.log('get',prop);
    return Reflect.get(obj,prop);
  },});
a.push(1);

get [1,2] push
get [1,2] length
set [1,2] 2 1
set [1,2,1] length 3

當我們代理了一個數組之後,直接呼叫push插入一個新的資料,能夠明顯的看到getter跟setter都會被呼叫兩次,一次是呼叫的push方法,而另一次是陣列的長度length,也就是說,proxy不僅僅會檢測到我們當前呼叫的方法,還能夠知道我們的資料長度是否發生了變化

看到這邊,可能會有一個疑惑,push是對當前陣列進行的操作,但是數組裡面還有部分方法是會返回一個新的陣列,proxy是否會對新生成的陣列也進行代理,這裡我們拿splice舉個例子

// a= [1,2]
a.splice(0,1)

get [1,2] length
get [1,2] constructor
get [1,2] 0
get [1,2] 1
set [1,2] 0 2
set [2,empty] length 1

從表現形式來看,proxy代理之後的陣列只會對當前陣列的內容進行監聽,也就是呼叫splice之後新生成的陣列的變化是不會被代理的

現在我們回過頭來看下vue3的trigger方法,這個是vue在set完成之後觸發的依賴更新,同樣的掐個頭去個尾,除去正常的執行以外,我們看下針對陣列做的優化

// add方法是將當前的依賴項新增進一個等待更新的陣列中
else if (key === 'length' && isArray(target)) {
  depsMap.forEach((dep,key) => {
   if (key === 'length' || key >= (newValue as number)) {
    add(dep)
   }
  })
} 

由於我們知道, 在一次運算元組的時候會進行多次的set,那麼如果每次set都要去更新依賴的話,會造成效能上的浪費,所以在vue3裡面只有在set length的時候才會去呼叫add方法,然後統一執行所有的更新

結語

不得不說,proxy比defineProperty強大了太多,不僅解決了vue的歷史難題,讓vue的體驗更上了一層,更是去除了不少因為defineProperty而必須要的方法,精簡了vue的包大小

雖然proxy的相容性是比defineProperty差不少,但是在vue裡面基本已經拋棄了IE,所以如果你的專案需要在ie下執行的話,那就請放棄vue這個選擇,使用低版本的react的吧,哈哈哈哈哈哈

在移動端裡面基本上就是沒有這種版本的限制,實在是版本低不能使用proxy的話,相信去找找polyfill是能夠找到的

到此這篇關於vue3為什麼要用proxy替代defineProperty的文章就介紹到這了,更多相關vue3 proxy替代defineProperty內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!