你真的了解vue2.0 和 vue3.0 响应式原理区别吗?

7,187 阅读4分钟

1. Vue2.0 和 Vue3.0的原理

Vue2.0实现MVVM(双向数据绑定)的原理是通过 Object.defineProperty 来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。Vue 3.0实现响应式基于ES6的Proxy。两者的差异如下:

  • Vue2.0
  1. 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式。
  2. Object.defineProperty 无法检测到对象属性的添加和删除 。
  3. 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式。
  4. 深度监听需要一次性递归,对性能影响比较大。
  • Vue3.0
  1. 基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。
  2. 不需要一次性遍历data的属性,可以显著提高性能。
  3. 因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11 。

2. Vue2.0 响应式的实现

  1. 由于Object.defineProperty 无法监听数组,所以数组类型实现响应式,需要处理。 判断如果是数组类型,就重写数组的原型方法('push','pop','shift'等方法)
 // 重新定义数组原型,Object.defineProperty不具备监听数组的方法
 const oldArrayProperty = Array.prototype;
     const arrProto = Object.create(oldArrayProperty);
     ["push","pop","shift","unshift","splice"].forEach(
         methodName => 
         (arrProto[methodName] = function() {
             updateView();
             oldArrayProperty[methodName].call(this, ...arguments);
         })
     )
  1. 将传入的data属性进行深度监听,判断是对象还是数组。
 function observer(target){
     if(typeof target !== 'object' || target === null){
         return target
     }
 
     // 如果是数组类型,重写数组原型的方法("push","pop","shift","unshift","splice")
     if(Array.isArray(target)){
         target.__proto__ == arrProto;
     }
 
     // 如果是对象,遍历对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter
     for(let key in target){
         defineReactive(target,key,target[key])
     }
 }
  1. 核心API Object.defineProperty,将传入属性转为getter/setter
function defineReactive(target, key, value){
    // 如果对象有更多的层级,再次调用observer监听方法,实现深层次的监听。
    observer(value);

    Object.defineProperty(target, key, {
        get(){
            return value;
        },
        set(newValue){
            // 设置值的时候也需要深度监听
            observer(value);

            if(newValue !== value){
                value = newValue;

                // 数据驱动视图,如果数据改变,就调用视图更新的方法。对应到Vue中是执行VDOM
                updateView();
            }
        }
    })
}
  1. 数据更新会触发视图更新,这是MVVM的绑定原理,这就会涉及到Vue的template编译为render函数,在执行Virtual Dom, Diff算法等内容。
 function updateView(){
     console.log('视图更新')
 }
  1. 使用实例
const data = {
  name: "zhangsan",
  age: 20,
  info: {
    address: "北京" // 需要深度监听
  },
  nums: [10, 20, 30]
};
observer(data);

3. Vue3.0 响应式的实现

Vue3.0 基于Proxy来做数据大劫持代理,可以原生支持到数组的响应式,不需要重写数组的原型,还可以直接支持新增和删除属性, 比Vue2.x的Object.defineProperty更加的清晰。

 const proxyData = new Proxy(data, {
   get(target,key,receive){ 
     // 只处理本身(非原型)的属性
     const ownKeys = Reflect.ownKeys(target)
     if(ownKeys.includes(key)){
       console.log('get',key) // 监听
     }
     const result = Reflect.get(target,key,receive)
     return result
   },
   set(target, key, val, reveive){
     // 重复的数据,不处理
     const oldVal = target[key]
     if(val == oldVal){
       return true
     }
     const result = Reflect.set(target, key, val,reveive)
     return result
   },
   // 删除属性
   deleteProperty(target, key){
     const result = Reflect.deleteProperty(target,key)
     return result
   }
 })

使用实例:

 const data = {
   name: "zhangsan",
   age: 20,
   info: {
     address: "北京" // 需要深度监听
   },
   nums: [10, 20, 30]
 };

4. 核心源码

  1. vue2.0 核心源码
function defineReactive(target, key, value) {
   //深度监听
   observer(value);
 
   Object.defineProperty(target, key, {
     get() {
       return value;
     },
     set(newValue) {
       //深度监听
       observer(value);
       if (newValue !== value) {
         value = newValue;
 
         updateView();
       }
     }
   });
 }
 
 function observer(target) {
   if (typeof target !== "object" || target === null) {
     return target;
   }
 
   if (Array.isArray(target)) {
     target.__proto__ = arrProto;
   }
 
   for (let key in target) {
     defineReactive(target, key, target[key]);
   }
 }
 
 // 重新定义数组原型
 const oldAddrayProperty = Array.prototype;
 const arrProto = Object.create(oldAddrayProperty);
 ["push", "pop", "shift", "unshift", "spluce"].forEach(
   methodName =>
     (arrProto[methodName] = function() {
       updateView();
       oldAddrayProperty[methodName].call(this, ...arguments);
     })
 );
 
 // 视图更新
  function updateView() {
   console.log("视图更新");
 }
 
 // 声明要响应式的对象
 const data = {
   name: "zhangsan",
   age: 20,
   info: {
     address: "北京" // 需要深度监听
   },
   nums: [10, 20, 30]
 };
 
 // 执行响应式
 observer(data);
  1. vue3.0 核心源码
  // 声明要响应式的对象,Proxy会自动代理
 const data = {
   name: "zhangsan",
   age: 20,
   info: {
     address: "北京" // 需要深度监听
   },
   nums: [10, 20, 30]
 };
 
const proxyData = new Proxy(data, {
   get(target,key,receive){ 
     // 只处理本身(非原型)的属性
     const ownKeys = Reflect.ownKeys(target)
     if(ownKeys.includes(key)){
       console.log('get',key) // 监听
     }
     const result = Reflect.get(target,key,receive)
     return result
   },
   set(target, key, val, reveive){
     // 重复的数据,不处理
     const oldVal = target[key]
     if(val == oldVal){
       return true
     }
     const result = Reflect.set(target, key, val,reveive)
     console.log('set', key, val)
     return result
   },
   deleteProperty(target, key){
     const result = Reflect.deleteProperty(target,key)
     console.log('delete property', key)
     console.log('result',result)
     return result
   }
 })
 
console.log(proxyData.age);    // 20
console.log(proxyData.info);   // { address: '北京' }