响应式

  • Object.defineProperty
  • 观察者模式

响应式原理

流程

  1. Watcher 和 Dep
  2. 我们将更新的功能封装了一个 watcher
  3. 渲染页面前,会将当前 watcher 放到 Dep 类上
  4. 在 vue 中页面渲染时使用的属性,需要进行依赖收集,收集对象的渲染 watcher
  5. 取值时,给每个属性都加了个 dep 属性,用于存储这个渲染 watcher(同一个 watcher 会对应多个 dep)
  6. 每个属性可能对应多个视图(多个视图肯定是多个 watcher),一个属性要对应多个 watcher
  7. dep.depend() => Dep.target.addDep() => 通知 watcher 存放 dep => dep.addSub() => 通知 dep 存放 watcher
  8. 双向存储

原理

  • defineProperty
  • 观察者模式
// 1. 数据初始化
function Vue(options) {
  this._init(options);
}
Vue.prototype._init = function (options) {
  const vm = this;
  initState(vm);
};
function initState(vm) {
  initData(vm);
}
function initData(vm) {
  let data = vm.$options.data;
  // 省略data函数调用,数据代理
  observe(data);
}

// 2. 数据劫持
function observe(data) {
  return new Observer(data);
}
class Observer {
  constructor(value) {
    if (Array.isArray(value)) {
      value.__proto__ = arrayMethods; // AOP
      this.observeArray(value); // 递归调用每一项item
    } else {
      this.walk(value); // defineReactive
    }
  }
}
// 核心 递归进行依赖收集
function defineReactive(data, key, value) {
  let childOb = observe(value); // value是嵌套对象时递归
  let dep = new Dep(); // 每个属性一个Dep实例
  Object.defineProperty(data, key, {
    get() {
      // Dep.target指当前watcher
      if (Dep.target) {
        dep.depend(); // 保存当前dep实例
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value); // 递归收集依赖
          }
        }
      }
    },
    set(newValue) {
      if (newValue === value) {
        return;
      }
      observe(newValue);
      value = newValue;
      dep.notify(); // 派发更新,给渲染watcher
    },
  });
}

性能

  • 嵌套层次不能太深,否则有大量递归
  • Object.defineProperty 对于不存在的属性不会拦截,也不会响应,可以使用 vm.$set API 让对象自己去 notify,或者赋予一个新对象
  • 数组改索引和length 不触发更新,可以通过 7 个变异方法;数组中如果是对象类型,修改对象可以更新视图