解析Vue2.0和3.0的响应式原理和异同(带源码)

前言

2019.10.5日发布了Vue3.0,如今2020年了,估计Vue3.0正式版也快出来了。html

2.0跟3.0的变化也挺大的,vue

  • 结构: 2.0用Flex ,3.0用 TypeScript
  • 性能: 3.0优化了Virtual Dom的算法。
  • 响应式原理:2.0用 Object.defineProperty,3.0用Proxy
  • ...

Vue2.0和Vue3.0实现原理

  1. Vue 2.0

    Vue2.0实现MVVM(双向数据绑定)的原理是经过 Object.defineProperty 来劫持各个属性的setter、getter,在数据变更时发布消息给订阅者,触发相应的监听回调。node

    Vue官网也给出了解释:react

  2. Vue 3.0 实现响应式基于ES6: Proxy

Vue2.0和Vue3.0的差别以下:

Vue2.0

  • 基于Object.defineProperty,不具有监听数组的能力,须要从新定义数组的原型来达到响应式。
  • Object.defineProperty 没法检测到对象属性的添加和删除 。
  • 因为Vue会在初始化实例时对属性执行getter/setter转化,全部属性必须在data对象上存在才能让Vue将它转换为响应式。
  • 深度监听须要一次性递归,对性能影响比较大。

Vue3.0

  • 基于ProxyReflect,<能够原生监听数组,能够监听对象属性的添加和删除。
  • 不须要一次性遍历data的属性,能够显著提升性能。
  • 由于Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11 。

Vue2.x实现响应式

下面是基于Object.defineProperty ,一步步实现简单版Vue2.0。算法

  1. 因为Object.defineProperty 没法监听数组,因此数组类型实现响应式,须要处理。

判断若是是数组类型,就重写数组的原型方法('push','pop','shift',unshift)chrome

// 从新定义数组原型,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();
                  }
              }
          })
      }
  2. 数据更新会触发视图更新,这是MVVM的绑定原理,这就会涉及到Vue的 template 编译为 render 函数,在执行 Virtual Dom, Diff算法, Vnode等 这些东西了。浏览器

    function updateView(){
          console.log('视图更新')
      }

5.使用ide

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

observer(data);

Vue3.0实现响应式

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

  1. 核心代码(很是少)

    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
      }
    })
  2. 使用

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

    直接这样就能够了,也不须要声明,Proxy直接会代理监听data的内容,很是的简单方便,惟一的不足就是部分浏览器没法兼容Proxy,也不能hack,因此目前只能兼容到IE11。

所有源码

可直接将代码复制到chrome浏览器的控制台,直接调试打印。
  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);
  2. Vue3.0

    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
      }
    })
    
     // 声明要响应式的对象,Proxy会自动代理
    const data = {
      name: "zhangsan",
      age: 20,
      info: {
        address: "北京" // 须要深度监听
      },
      nums: [10, 20, 30]
    };
相关文章
相关标签/搜索