Vue2.6.14源码学习:Vue.set的实现原理

181 阅读2分钟

Vue.set 是什么?

set 的三个参数:target、key、value

  • {Object | Array} target 数据源对象或者是数组
  • {string | number} key 数据源新增的属性或者是替换数组项下标
  • {any} value 新增或修改的值

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

更具体点这里👈

new Vue({
  data() {
    return {
      person: {
        name: "张三",
      },
    };
  },
  created() {
    // 普通的新增属性并不会具备响应式
    this.person.age = 18;
    // this.$set(this.person, 'age', 18)
  },
}).$mount("#app");

Vue.set 的实现原理

源码目录:/src/core/observer/index

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 *
 * 设置对象的属性。 添加新属性并在该属性尚不存在时触发更改通知。
 */
export function set(target: Array<any> | Object, key: any, val: any): any {
  /*
     process.env.NODE_ENV:判断当前开发环境
     isUndef: 判断数据源是否不存在
     isPrimitive:判断是否是基础数据类型
  */
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }

  /*
    isValidArrayIndex:判断key是否是一个索引
  */
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    /*
     Math.max 取数据中最大那个那个值
     扩展:
      Math.max以及Math.min一个取最大值,一个取最小值,可用于数组
      let arr = [1,3,5,6,2,4,8,10,9,4]
      Math.max.apply(Math,arr)
      Math.min.apply(Math,arr)
    */
    target.length = Math.max(target.length, key);
    /*
      根据key的下标直接替换val,如果key超出target长度,进行扩展的同时
      也会添加相对应的空位
    */
    target.splice(key, 1, val);
    return val;
  }
  // 判断key在数据源里面并且不是 Object.prototype 的属性
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  // ob 是一个观察者对象
  const ob = (target: any).__ob__;
  // 数据源不能是Vue实列
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "[避免向 Vue 实例或其根 $data 添加响应式属性]Avoid adding reactive properties to a Vue instance or its root $data " +
          "at runtime - declare it upfront in the data option."
      );
    return val;
  }
  // 观察者对象不存在表示是一个普通的对象
  if (!ob) {
    target[key] = val;
    return val;
  }
  // 让新增的属性具备响应式
  defineReactive(ob.value, key, val);
  // 通知属性改变
  ob.dep.notify();
  return val;
}

总结

Vue.set 会优先判断数据源target是否存在(不为 null 或者 undefined)并且需要是一个复杂数据类型,然后对target是否是数组或者对象分开判断

  • 数组Array

    判断target是否是数组并且 key 是否是索引,然后直接调用 splice 方法进行替换或赋值,需要注意的是,如果 key 索引大于target的长度,那么除了target的长度会直接等于 key 以外,数组中也会添加相对应的空位

  • 对象Object

    • key 存在

      如果 key 在target中存在并且不是一个 Object 的属性关键字,那么就会直接赋值

    • key 不存在

      利用Object.defineProperty数据双向劫持让新增的属性具备响应式