揭幕Vue3的proxy能做到速度加倍記憶體減半原因

大雄沒了哆啦A夢發表於2018-12-06


image

瞭解過vue3更新內容的同學應該都知道,vue3的整個資料監聽系統都進行了重構,由es5的Object.defineProperty改為了es6的proxy。尤大說,這個新的資料監聽系統帶來了初始化速度加倍同時記憶體佔用減半的效果。本文就聊聊,為啥proxy可以做到速度加倍的同時,記憶體還能減半。

vue初始化過程

揭幕Vue3的proxy能做到速度加倍記憶體減半原因

我們知道,vue的初始化過程,有三座大山,分別為Observer、Compiler和Watcher,當我們new Vue的時候,會呼叫Observer,通過Object.defineProperty來對vue物件的data,computed或者props(如果是元件的話)的所有屬性進行監聽,同時通過compiler解析模板指令,解析到屬性後就new一個Watcher並繫結更新函式到watcher當中,Observer和Compiler就通過屬性來進行關聯,如此,當Observer中的setter檢測到屬性值改變的時候,就呼叫屬性對應的所有watcher,呼叫更新函式,從而更新到屬性對應的dom。 各位有興趣的,可以看下我的另外一片文章

這裡面有兩點需要強調下:
1、Object.defineProperty需要遍歷所有的屬性,這就造成了如果vue物件的data/computed/props中的資料規模龐大,那麼遍歷起來就會慢很多。
2、同樣,如果vue物件的data/computed/props中的資料規模龐大,那麼Object.defineProperty需要監聽所有的屬性的變化,那麼佔用記憶體就會很大。

Object.defineProperty VS Proxy

Object.definePropety的缺點

除了上面講,在資料量龐大的情況下Object.defineProperty的兩個缺點外,Object.defineProperty還有以下缺點。
1、無法監聽es6的Set、WeakSet、Map、WeakMap的變化;
2、無法監聽Class型別的資料;
3、屬性的新加或者刪除也無法監聽;
4、陣列元素的增加和刪除也無法監聽。

Proxy應運而生

針對Object.defineProperty的缺點,Proxy都能夠完美得解決,它唯一的缺點就是,對IE不友好,所以vue3在檢測到如果是使用IE的情況下(沒錯,IE11都不支援Proxy),會自動降級為Object.defineProperty的資料監聽系統。所以如果是IE使用者,那麼就享受不到速度加倍,記憶體減半的體驗了。 揭幕Vue3的proxy能做到速度加倍記憶體減半原因


初始化對比

Object.defineProperty初始化

const data = {}
for(let i = 0; i <= 100000; i++) {
  data['pro' + i] = i
}

function defineReactive(data, property) {
  let value = data[property]
  Object.defineProperty(data, property, {
    get() {
      // console.log(`讀取${property}的值為${value}`)
      return value
    },
    set(newVal) {
      // console.log(`更新${property}的值為${newVal}`)
    }
  })
}

for(let property in data) {
  defineReactive(data, property)
}
複製程式碼

Proxy初始化

const data = {}
for(let i = 0; i <= 100000; i++) {
  data['pro' + i] = i
}
var proxyData = new Proxy(data, {
  get(target, property, receiver) {
    // console.log(`讀取${property}的值為${target[property]}`)
    return Reflect.get(target, property, receiver);
  },
  set(target, property, value, receiver) {
   //  console.log(`更新${property}的值為${value}`)
    return Reflect.set(target, property, value, receiver);
  }
})
複製程式碼

通過devtool的performace分別對初始化和記憶體佔用進行對比

分別例項化有1000/10000/100000/1000000個屬性的物件的時間對比 image分別例項化有1000/10000/100000/1000000個屬性的物件的記憶體對比 imagedp=defineObjectProperty

通過對比我們知道,Proxy確實可以做到初始化速度加倍,記憶體佔用減半的效果,為啥能做到這兩點,請看開頭我強調的兩點,所以期待vue3的到來吧。

暴露響應式API

vue3中暴露了響應式API給使用者進行資料的監控,eg:

import {observable, effect} from 'vue'

const state = observable({
    count: 0
})

effect(() => {
    console.log(`count is: ${state.count}`)
})// count is: 0

state.count++ // count is: 1
複製程式碼

根據這部分程式碼,我自己也以proxy實現了一個簡單版的,有興趣的瞭解下:

// cbs是Vue的一個靜態屬性,儲存了effect的所有回撥
let Vue = function() {}
Vue.prototype._cbs = []

/**
* 返回一個代理的物件,引數是需要被代理的物件
**/
const observable = (obj) => {
  return new Proxy(obj, {
    get(target, property, receiver) {
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      // 取出所有effect的回撥並執行
      for(let cb of Vue.prototype._cbs) {
        cb(value)
      }
      return Reflect.set(target, property, value, receiver)
    }
  })
}

/**
* 只要observable的代理物件屬性改變,就會執行effect的回撥
**/
const effect = (cb) => {
  Vue.prototype._cbs.push(cb)
}

let state = observable({
  count: 0
})

effect((value) => {
  console.log(`count is ${value}`)
})
state.count++複製程式碼


相關文章