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


    前言

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

    2.0跟3.0的变化也挺大的,

    • 结构: 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,在数据变动时发布消息给订阅者,触发相应的监听回调。

      Vue官网也给出了解释:

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

    Vue2.0和Vue3.0的差异如下:

    Vue2.0

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

    Vue3.0

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

    Vue2.x实现响应式

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

    1. 由于Object.defineProperty 无法监听数组,所以数组类型实现响应式,需要处理。

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

        // 重新定义数组原型,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.使用

    
    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]
      };
      
      
     
  • 相关阅读:
    CyclicBarrier与CountDownLatch区别
    导入搜狗实验室新闻语料库
    安装ik分词插件
    分页显示时传递页码的方法
    elasticsearch安装步骤
    linux查看端口占用情况
    Python:文件的读取、创建、追加、删除、清空
    R语言-选择样本数量
    不符合正态分布的配对数据也有自己的统计方法。
    python时间处理
  • 原文地址:https://www.cnblogs.com/chenhuichao/p/13529429.html
Copyright © 2020-2023  润新知