vue是如何对数组方法进行变异的

由于 js 的限制,在数组使用pushpopsplice等这些方法时,其set方法是不能感知的,例如如下代码(vue js 中精简的部分):

function defineReactive(obj, key, val) {
  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }
  const getter = property && property.get;
  const setter = property && property.set;

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log("取值操作");
      return getter ? getter.call(obj) : val;
    },
    set(v) {
      console.log(`设置值${v}`);
      if (setter) {
        setter.call(obj, v);
      } else {
        val = v;
        return val;
      }
    }
  });
}

此时有如下数据,然后进行相关操作

defineReactive(data, "array", data.array);
const a = data.array[0]; // 取值操作
data.array[0] = 10; // 取值操作
data.array.push(1); // 取值操作
data.array.splice(1); // 取值操作
data.array = [4, 5, 6]; // 设置值4,5,6

从结果可以看出,除了赋值操作,其余的方式都不会被setter检测到。那么 vue 是怎么处理让其支持数组的相关检测的?

其主要是对数组的这些方法进行变异。以数组的push操作为例。

const arrayMethod = Object.create(Array.prototype);
const originPush = arrayMethod.push;
Object.defineProperty(arrayMethod, "push", {
  enumerable: true,
  configurable: true,
  writable: true,
  value(...args) {
    const result = originPush.apply(this, args);
    console.log(`对数组push了${JSON.stringify(args)}`);
    return result;
  }
});

这里,首先将数组的原型放到arrayMethod的原型中,然后给arrayMethod定义push方法,在给数组push值之前,会先调用数组原型上的push方法,保证数组正常执行push方法,然后,再执行剩下的自定义方法,这样就实现了对数组的变异。

对操作数组的方法都执行这样的操作,就实现了类似 Vue 对数据的响应。

以下代码节选自Vue源码。

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];

methodsToPatch.forEach(function(method) {
  // cache original method
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if (inserted) ob.observeArray(inserted);
    // notify change
    ob.dep.notify();
    return result;
  });
});
如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注