4 Vue3.x 响应式数据原理

Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

proxy基本用法

    // proxy默认只会代理第一层对象,只有取值再次是对象的时候再次代理,不是一上来就代理,提高性能。不像vue2.x递归遍历每个对象属性
    let handler = {
        set(target, key, value) {
            return Reflect.set(target, key, value);
        },
        get(target, key) {
            if (typeof target[key] == 'object' && target[key] !== null) {
                return new Proxy(target[key], handler); // 懒代理,只有取值再次是对象的时候再次代理,提高性能
            }
            return Reflect.get(target, key);
        }
    }
    let obj = { school: { name: 'poetry', age: 20 } };
    let proxy = new Proxy(obj, handler);
    
    // 返回对象的代理
    proxy.school

说说你对 proxy 的理解,Proxy 相比于 defineProperty 的优势

Object.defineProperty() 的问题主要有三个:

  • 不能监听数组的变化 :无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应
  • 必须遍历对象的每个属性 :只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果属性值是对象,还需要深度遍历。Proxy 可以劫持整个对象,并返回一个新的对象
  • 必须深层遍历嵌套的对象

Proxy的优势如下:

  • 针对对象:针对整个对象,而不是对象的某个属性 ,所以也就不需要对 keys 进行遍历
  • 支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的
  • Proxy的第二个参数可以有 13 种拦截方:不限于applyownKeysdeletePropertyhas等等是Object.defineProperty不具备的
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
  • Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

proxy详细使用点击查看 (opens new window)

Object.defineProperty的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平

defineProperty的属性值有哪些

    Object.defineProperty(obj, prop, descriptor)
    
    // obj 要定义属性的对象
    // prop 要定义或修改的属性的名称
    // descriptor 要定义或修改的属性描述符
    
    Object.defineProperty(obj,"name",{
      value:"poetry", // 初始值
      writable:true, // 该属性是否可写入
      enumerable:true, // 该属性是否可被遍历得到(for...in, Object.keys等)
      configurable:true, // 定该属性是否可被删除,且除writable外的其他描述符是否可被修改
      get: function() {},
      set: function(newVal) {}
    })

相关代码如下

    import { mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
    import { isObject } from "./util"; // 工具方法
    
    export function reactive(target) {
      // 根据不同参数创建不同响应式对象
      return createReactiveObject(target, mutableHandlers);
    }
    function createReactiveObject(target, baseHandler) {
      if (!isObject(target)) {
        return target;
      }
      const observed = new Proxy(target, baseHandler);
      return observed;
    }
    
    const get = createGetter();
    const set = createSetter();
    
    function createGetter() {
      return function get(target, key, receiver) {
        // 对获取的值进行放射
        const res = Reflect.get(target, key, receiver);
        console.log("属性获取", key);
        if (isObject(res)) {
          // 如果获取的值是对象类型,则返回当前对象的代理对象
          return reactive(res);
        }
        return res;
      };
    }
    function createSetter() {
      return function set(target, key, value, receiver) {
        const oldValue = target[key];
        const hadKey = hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if (!hadKey) {
          console.log("属性新增", key, value);
        } else if (hasChanged(value, oldValue)) {
          console.log("属性值被修改", key, value);
        }
        return result;
      };
    }
    export const mutableHandlers = {
      get, // 当获取属性时调用此方法
      set, // 当修改属性时调用此方法
    };

Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger

Last Updated:
Contributors: leeguooooo