Vue3 爲何使用 Proxy 實現數據監聽

博客地址:https://ainyi.com/93

vue3 響應式數據放棄了 Object.defineProperty,而使用Proxy來代替它

我們知道,在 vue2 中,實現數據監聽是使用Object.defineProperty --> 實現方法可看:vue 數據雙向綁定原理

而這個方法有缺點,並且不能實現數組和對象的部分監聽情況;具體也可以看我之前寫的一篇博客:關於 Vue 不能 watch 數組 和 對象變化的解決方案

最新的 Proxy,相比 vue2 的 Object.defineProperty,能達到速度加倍內存減半的成效。
具體是怎麼實現、以及對比舊的實現方法爲啥能有速度加倍、內存減半的特性

下面來聊聊

Vue 初始化過程

Vue 的初始化過程,分別有ObserverCompilerWatcher
當我們 new Vue 的時候,會調用Observer,通過 Object.defineProperty 遍歷 vue 對象的 data、computed 或者 props(如果是組件的話)的所有屬性進行監聽。同時通過Compiler解析模板指令,解析到屬性後就 new 一個Watcher並綁定更新函數到 watcher 當中,Observer 和 Compiler 就通過屬性來進行關聯

WechatIMG198.png

如上,當 Observer 中的 setter 檢測到屬性值改變的時候,就調用屬性對應的所有 watcher 調用更新函數,從而更新到屬性對應的 dom

Object.defineProperty

來個簡單的 Object.defineProperty 例子

class Observer {
  constructor(data) {
    // 遍歷參數 data 的屬性,給添加到 this 上
    for(let key of Object.keys(data)) {
      if(typeof data[key] === 'object') {
        data[key] = new Observer(data[key]);
      }
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          console.log('你訪問了' + key)
          return data[key]
        },
        set(newVal) {
          console.log('你設置了' + key)
          console.log('新的' + key + '=' + newVal)
          if(newVal === data[key]) {
            return
          }
          data[key] = newVal
        }
      })
    }
  }
}

const obj = {
    name: 'app',
    age: '18',
    a: {
        b: 1,
        c: 2,
    },
}
const app = new Observer(obj);
app.age = 20;
console.log(app.age);
app.newPropKey = '新屬性';
console.log(app.newPropKey);

輸出如下:

你設置了age
新的age=20
你訪問了age
20
新屬性

從上面可以知道:

  1. Object.defineProperty 需要遍歷所有的屬性,這就造成了如果 vue 對象的 data/computed/props 中的數據規模龐大,那麼遍歷起來就會慢很多
  2. 同理,如果 vue 對象的 data/computed/props 中的數據規模龐大,那麼 Object.defineProperty 需要監聽所有的屬性的變化,那麼佔用內存就會很大

Proxy

再來看看 Proxy

Proxy 對象用於定義基本操作的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)

可以理解爲在對象之前設置一個”攔截“,當監聽的對象被訪問的時候,都必須經過這層攔截。可以在這攔截中對原對象處理,返回需要的數據格式
也就是無論訪問對象的什麼屬性,之前定義的或是新增的屬性,都會走到攔截中進行處理。這就解決了之前所無法監聽的問題

官方例子:

const p = new Proxy(target, handler)

參數

  • target: 要使用 Proxy 包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)
  • handler:一個通常以函數作爲屬性的對象,各屬性中的函數分別定義了在執行各種操作時代理 p 的行爲

來個實際 Proxy 例子

const obj = {
  name: 'krry',
  age: 24,
  others: {
    mobile: 'mi10',
    watch: 'mi4'
  }
}
const p = new Proxy(obj, {
  get(target, key, receiver) {
    console.log('查看的屬性爲:' + key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log('設置的屬性爲:' + key);
    console.log('新的屬性:' + key, '值爲:' + value);
    Reflect.set(target, key, value, receiver);
  }
})
p.age = 22
console.log(p.age)
console.log('--------')
p.single = 'NO'
console.log(p.single)
console.log('--------')
p.others.shoe = 'boost'
console.log(p.others.shoe)

輸出如下:

設置的屬性爲:age
新的屬性:age 值爲:22
查看的屬性爲:age
22
--------
設置的屬性爲:single
新的屬性:single 值爲:NO
查看的屬性爲:single
NO
--------
查看的屬性爲:others
查看的屬性爲:others
boost

由上可知,新增或編輯屬性,並不需要重新添加響應式處理,都能監聽的到
因爲 Proxy 是對對象的操作,只要你訪問對象,就會走到 Proxy 的邏輯中

Reflect 是一個內置的對象,它提供攔截 JavaScript 操作的方法。這些方法與proxy handlers的方法相同。Reflect不是一個函數對象,因此它是不可構造的

區別

Proxy 和 Object.defineProperty 的使用方法看似很相似,其實 Proxy 是在更高維度上去攔截屬性的

Object.defineProperty

Vue2 中,對於給定的 data:如 { count: 1 },是需要根據具體的 key 也就是 count,去對 get 和 set 進行攔截,也就是:

Object.defineProperty(data, 'count', {
  get() {},
  set() {},
})

必須預先知道要攔截的 key 是什麼,這也就是爲什麼 Vue2 裏對於對象上的新增屬性無能爲力,所以 Vue 初始化的過程中需要遍歷 data 來挾持數據變化,造成速度變慢,內存變大的原因


Proxy

而 Vue3 所使用的 Proxy,則是這樣攔截的:

new Proxy(data, {
  get(key) { },
  set(key, value) { },
})

可以看到,proxy 不需要關心具體的 key,它去攔截的是 修改 data 上的任意 key讀取 data 上的任意 key
所以,不管是已有的 key 還是新增的 key,都會監聽到
但是 Proxy 更加強大的地方還在於 Proxy 除了 get 和 set,還可以攔截更多的操作符,具體可看 MDN

兼容性

Proxy 對 IE 不友好,vue3 在檢測到使用 IE 的情況下(包括 IE11),會自動降級爲 Object.defineProperty 的數據監聽系統

博客地址:https://ainyi.com/93

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章