响应式原理

数据代理

什么是数据代理

通过中间对象代理另一个对象中属性的读写操作。

如何使用数据代理

Vue 通过数据代理实现vm.name访问 data 对象里的属性,原理就是 Object.defineProperty API。

  • Object.defineProperty遍历 data 将属性添加到 vm 上
  • 为 vm 属性指定 getter/setter 进行读写操作
let vm = new Vue({
  el: "#add",
  data: {
    name: "zhang13pro",
    age: 24,
  },
});

数据劫持

什么是数据劫持

Vue 在初始化的时候会将 data option 里的数据进行 reactive,本质就是递归给每一项属性设置 setter/getter。简单的数据劫持 ✍️

let data = {
  name: "zhang13pro",
  age: 24,
};

new Observer(data);

function Observer(obj) {
  let keys = Object.keys(obj);

  keys.forEach((k) =>
    Object.defineProperty(this, k, {
      get() {
        return obj[k];
      },
      set(val) {
        obj[k] = val;
      },
    })
  );
}

数据劫持的局限性

对于数组类型的数据,Vue 不会对其变更进行 setter/getter,也就是说对于形如array[0] = '新元素'的变更,Vue 是无法数据绑定更新页面的。

Vue wraps an observed array's mutation methods so they will also trigger view updates.

对此,Vue 拦截了 Array.prototype 进行包装,重写了数组的七个修改数组的变更方法,使用它们让数组的数据绑定重新生效。

数组更改是如何实现的

拦截原始数组方法,进行额外操作,包装器模式、面向切面编程思想(AOP):

let arrayProto = Array.prototype;
// 不破坏封装的前提下,动态的扩展功能
let arrayMethods = Object.create(arrayProto); // arrayMethods.__proto__ = arrayProto
let methodsToWrap = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "reverse",
  "sort",
];

methodsToWrap.forEach((method) => {
  arrayMethods[method] = function (...args) {
    // 保留原始数组方法的结果
    let result = arrayProto[method].apply(this, args);

    // 数据绑定
    switch (method) {
      case "push":
        xxx;
        break;
      case "unshift":
        xxx;
        break;
      case "splice":
        xxx;
      default:
        break;
    }

    return result;
  };
});

参考

Vue 为什么不能检测数组和对象的变化open in new window