解释 Vue 2 中为什么对数组的 `push`, `pop`, `shift`, `unshift`, `splice`, `sort`, `reverse` 等方法进行重写,并分析其源码实现。

各位观众,各位朋友,欢迎来到今天的“Vue 2 数组变异方法大揭秘”特别节目!我是你们的老朋友,老司机Vue,今天咱们就来聊聊Vue 2里那些被“动过手脚”的数组方法,看看它们到底经历了什么,又为我们做了些什么。

准备好了吗?发车啦!

为什么要重写数组方法?

首先,咱们得搞清楚一个大前提:Vue 2 的核心是数据响应式。这意味着,当你修改了某个数据,Vue 就能立刻知道,然后自动更新视图。

但是,JavaScript 的原始数组方法,它们修改数组后,并不会主动通知 Vue “嘿,老弟,我改了,快更新一下!”。这就像你偷偷换了邻居家的电视,但他毫不知情,还傻乎乎地看着原来的旧电视。

为了解决这个问题,Vue 就需要“监听”数组的变化。但是直接 Object.defineProperty 来劫持数组的每一个索引是不现实的,性能开销太大。所以,Vue 选择了“曲线救国”的策略:重写数组的变异方法(mutation methods)。

所谓的变异方法,就是指那些会直接修改数组自身的方法,比如 push, pop, shift, unshift, splice, sort, reverse

重写后的数组方法能做什么?

重写后的数组方法,主要做了两件事:

  1. 调用原始方法: 确保数组的原始功能不受影响。毕竟,我们只是想“监听”,而不是“破坏”。
  2. 通知更新: 在原始方法执行完毕后,通知 Vue 进行视图更新。这才是重写的核心目的。

源码解剖:以 push 方法为例

咱们以 push 方法为例,来深入源码看看 Vue 是如何“动手动脚”的。

// 假设 Observer 类负责将数据转换为响应式数据
const arrayProto = Array.prototype; // 获取原始数组原型
const arrayMethods = Object.create(arrayProto); // 创建一个新的对象,继承自原始数组原型

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

methodsToPatch.forEach(function (method) {
  // 缓存原始方法
  const original = arrayProto[method];
  Object.defineProperty(arrayMethods, method, {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function mutator (...args) {
      const result = original.apply(this, args); // 调用原始方法
      const ob = this.__ob__; // 获取 Observer 实例
      let inserted;
      switch (method) {
        case 'push':
        case 'unshift':
          inserted = args;
          break;
        case 'splice':
          inserted = args.slice(2);
          break;
      }
      if (inserted) {
        ob.observeArray(inserted); // 对新增的元素进行响应式处理
      }
      ob.dep.notify(); // 通知更新
      return result;
    }
  });
});

// 在 Observer 类中,如果发现数据是数组,就替换它的原型
class Observer {
  constructor(value) {
    this.value = value;
    this.dep = new Dep(); // 用于收集依赖
    def(value, '__ob__', this); // 将 Observer 实例绑定到数组上,方便后续访问
    if (Array.isArray(value)) {
      value.__proto__ = arrayMethods; // 替换数组的原型
      this.observeArray(value); // 递归地将数组中的元素转换为响应式数据
    } else {
      this.walk(value);
    }
  }

  walk(obj) {
    // ...
  }

  observeArray(items) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i]); // observe函数用于将数据转换为响应式数据
    }
  }
}

// 定义一个不可枚举的属性
function def(obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  removeSub(sub) {
    // ...
  }

  depend() {
    if (window.target) { // window.target 指向当前的 watcher 实例
      window.target.addDep(this);
    }
  }

  notify() {
    const subs = this.subs.slice();
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update(); // 调用 watcher 的 update 方法
    }
  }
}

function observe(value) {
  if (typeof value !== 'object' || value === null) {
    return;
  }
  let ob;
  if (value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else {
    ob = new Observer(value);
  }
  return ob;
}

//模拟Watcher
class Watcher {
    constructor(vm, expOrFn, cb) {
        this.vm = vm;
        this.expOrFn = expOrFn;
        this.cb = cb;
        this.depIds = {}; // 用于防止重复依赖
        this.value = this.get(); // 获取初始值
    }

    get() {
        window.target = this; // 将当前 watcher 实例设置为全局 target
        let value = this.vm[this.expOrFn]; // 获取表达式的值
        window.target = null; // 清空 target
        return value;
    }

    addDep(dep) {
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this); // 将当前 watcher 添加到 dep 中
            this.depIds[dep.id] = dep;
        }
    }

    update() {
        const oldValue = this.value;
        this.value = this.get();
        this.cb.call(this.vm, this.value, oldValue); // 调用回调函数
    }
}

代码解读:

  1. arrayProtoarrayMethods: 首先,我们获取了原始的 Array.prototype,然后基于它创建了一个新的对象 arrayMethods。这样做的好处是,我们可以在 arrayMethods 上定义新的方法,而不会直接修改原始的 Array.prototype,避免污染全局环境。
  2. methodsToPatch: 这是一个数组,包含了需要重写的数组方法。
  3. forEach 循环: 循环遍历 methodsToPatch 数组,对每个方法进行重写。
  4. original: 缓存原始的数组方法。
  5. Object.defineProperty: 使用 Object.defineProperty 来定义新的方法。
    • enumerable: false:设置为不可枚举,避免在 for...in 循环中被遍历到。
    • configurable: true:设置为可配置,允许后续修改或删除。
    • writable: true:设置为可写,允许修改方法的值。
    • value:定义新的方法体(mutator 函数)。
  6. mutator 函数: 这是重写后的方法体,它做了以下几件事:
    • original.apply(this, args): 调用原始的数组方法,并传递参数。
    • this.__ob__: 获取 Observer 实例。每个被 Observer 观察的数据对象,都会有一个 __ob__ 属性指向它的 Observer 实例。
    • inserted: 处理新增的元素。例如,pushunshift 方法会新增元素,splice 方法也可能新增元素。我们需要对这些新增的元素进行响应式处理,也就是递归地调用 observe 函数。
    • ob.observeArray(inserted): 对新增的元素数组进行递归的响应式处理。
    • ob.dep.notify(): 调用 dep.notify() 方法,通知所有依赖该数组的 Watcher 实例进行更新。
    • return result: 返回原始方法的返回值。
  7. Observer 类: Observer类的作用就是用来观察数据,如果数据是数组,那么会给数组的原型指向arrayMethods,如果是对象,那么会遍历对象的每一个属性,使用Object.defineProperty来劫持属性的gettersetter
  8. Dep类和WatcherDep类用来收集依赖(watcher),Watcher类是观察者,当数据发生变化时,Dep会通知所有的Watcher进行更新。

其他数组方法的重写:

pop, shift, sort, reverse 这些方法的重写逻辑与 push 类似,都是先调用原始方法,然后通知更新。splice 方法稍微复杂一些,因为它既可以删除元素,也可以新增元素,所以需要同时处理删除和新增的情况。

表格总结:

| 方法 | 是否会修改数组自身 | 是否需要处理新增元素 | 备注
| —————– | ——————- | ——————- | —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-2024年1月27日
总结:

Vue 2 通过重写数组的变异方法,实现了对数组变化的“可感知”,从而能够更精准、更高效地更新视图,保证了数据驱动视图的响应式特性。这种“曲线救国”的策略,在性能和功能之间找到了一个很好的平衡点。

好了,今天的“Vue 2 数组变异方法大揭秘”节目就到这里。感谢大家的收看!咱们下期再见!

发表回复

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