揭幕Vue3的proxy能做到速度加倍内存减半原因

2,413 阅读3分钟


image

了解过vue3更新内容的同学应该都知道,vue3的整个数据监听系统都进行了重构,由es5的Object.defineProperty改为了es6的proxy。尤大说,这个新的数据监听系统带来了初始化速度加倍同时内存占用减半的效果。本文就聊聊,为啥proxy可以做到速度加倍的同时,内存还能减半。

vue初始化过程


我们知道,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用户,那么就享受不到速度加倍,内存减半的体验了。


初始化对比

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++