模拟实现 vue2 的数据绑定

vue2 主要是基于 Object.defineProperty 做的数据双向绑定,这里从 observer、watcher、dep 出发做一个简单的数据绑定实例

主要步骤

  1. observer 递归监听 data 对象的所有属性
  2. watcher 订阅器,视图、指令等需要绑定某个属性时会新建一个订阅器,并加入 Dep
  3. Dep 存放绑定某个属性的所有订阅器,可以添加订阅器和通知变化
  4. 数据更改时,通知 Dep 去执行相应的更新函数

vue原理图示

Dep

订阅器集合,也就是绑定了某个属性的视图或其他对象的集合。

class Dep {
  constructor() {
    this.deps = [];
  }
  // 添加订阅器
  addDep(dep) {
    this.deps.push(dep);
  }
  // 更新所有订阅器
  notify(newData) {
    for (let dep of this.deps) {
      dep.update(newData);
    }
  }
}

Observer

递归监听 data 对象的属性

// defineReactive 监听对象属性
function defineReactive(obj, key, val) {
  // deps 订阅器集合,作为闭包,方便处理订阅器的添加和更新操作
  let deps = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      // 判断是否需要添加订阅器
      // Dep.target 用于暂存订阅器
      Dep.target && deps.addDep(Dep.target);
    },
    set(newData) {
      // 对比数据,如果变化了就通知订阅器
      if (newData !== val) {
        deps.notify(newData);
      }
    },
  });
}
function observer(data) {
  if (!data || typeof data !== "object") {
    return null;
  }
  for (key in data) {
    if (data.hasOwnProperty(key)) {
      if (typeof data[key] === "object") {
        // 递归监听
        observer(data[key]);
      }
      defineReactive(data, key, data[key]);
    }
  }
}

Watcher

  • 绑定数据时,需要新建一个 watcher 实例,比如视图绑定、computed、watch
  • 订阅器初始化时,会自动将自己添加进 deps 里
// Watcher 订阅器
class Watcher {
  constructor(id, vm, key, cb) {
    console.log(`订阅器${id}`);
    this.id = id; // 订阅器标识
    this.vm = vm; // 当前环境
    this.cb = cb; // 更新回调
    this.key = key; // 当前监听属性
    // 触发getter,将自身添加到属性的订阅器数组deps中
    Dep.target = this;
    let val = vm.data[key]; // 强制执行 get,加入到订阅器集合
    Dep.target = null;
  }
  update(newData) {
    console.log(`订阅器${this.id}数据更新了,newData: ${newData}`);
    // this.cb()
  }
}

触发更新

// 新增订阅器
this.data = {
  name: "jack",
  action: "eating",
};
observer(this.data);
new Watcher(1, this, "name");
new Watcher(2, this, "action");
this.data.name = "Tom";