Vue学习笔记-简化版的 Vue 2 响应式系统实现

45 阅读1分钟

简化版的 Vue 2 响应式系统实现

  • Observer:递归劫持对象属性。
  • Dep:依赖收集和通知。
  • Watcher:依赖收集和更新回调。
  • defineReactive:核心的 Object.defineProperty 劫持。

数据读取 → 触发 getter → Dep.depend() → Watcher 被收集。
数据修改 → 触发 setter → dep.notify() → Watcher.update() → 视图更新。

// 1. Dep:依赖收集器
class Dep {
  constructor() {
    this.subs = []; // 存储所有依赖(Watcher)
  }

  // 添加依赖
  addSub(watcher) {
    if (watcher && !this.subs.includes(watcher)) {
      this.subs.push(watcher);
    }
  }

  // 移除依赖
  removeSub(watcher) {
    const index = this.subs.indexOf(watcher);
    if (index > -1) {
      this.subs.splice(index, 1);
    }
  }

  // 收集当前活跃的 Watcher
  depend() {
    if (Dep.target) {
      this.addSub(Dep.target);
    }
  }

  // 通知所有依赖更新
  notify() {
    // 创建副本,避免在 notify 过程中 watcher 被修改
    const subs = this.subs.slice();
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}

// 全局唯一正在执行的 Watcher,用于依赖收集
Dep.target = null;

// 用于在多个 Watcher 嵌套时保存和恢复当前目标 Watcher
const targetStack = [];

function pushTarget(watcher) {
  targetStack.push(Dep.target);
  Dep.target = watcher;
}

function popTarget() {
  Dep.target = targetStack.pop();
}

// 2. Observer:观察者,负责将对象变成响应式
class Observer {
  constructor(value) {
    this.value = value;
    // 给对象添加一个 dep,用于处理 vm.$set 或 vm.$delete 时通知更新
    this.dep = new Dep();
    // 标记已观察,避免重复观察
    def(value, '__ob__', this);

    if (Array.isArray(value)) {
      // 数组的特殊处理(这里简化,不展开)
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }

  // 遍历对象所有属性,转换为 getter/setter
  walk(obj) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]);
    }
  }

  observeArray(items) {
    for (let i = 0; i < items.length; i++) {
      observe(items[i]);
    }
  }
}

// 定义响应式属性
function defineReactive(obj, key, val) {
  // 每个属性都有自己的依赖收集器
  const dep = new Dep();

  // 如果值是对象,递归观察
  if (val && typeof val === 'object') {
    observe(val);
  }

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 依赖收集
      if (Dep.target) {
        dep.depend();
        if (val && val.__ob__) {
          // 如果值是对象,也收集依赖(处理 vm.$set 的情况)
          val.__ob__.dep.depend();
        }
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;

      val = newVal;

      // 如果新值是对象,也要变成响应式
      if (newVal && typeof newVal === 'object') {
        observe(newVal);
      }

      // 派发更新
      dep.notify();
    }
  });
}

// 尝试观察一个值
function observe(value) {
  if (!value || typeof value !== 'object') {
    return;
  }

  // 避免重复观察
  if (value.__ob__) {
    return value.__ob__;
  }

  return new Observer(value);
}

// 辅助函数:定义不可枚举的属性
function def(obj, key, val) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: false,
    writable: true,
    configurable: true
  });
}

// 3. Watcher:观察者,连接数据和视图
class Watcher {
  constructor(vm, expOrFn, cb, options = {}) {
    this.vm = vm;
    this.cb = cb;
    this.options = options;

    // 存储依赖的 dep
    this.deps = [];
    this.newDeps = [];
    this.depIds = new Set();
    this.newDepIds = new Set();

    // 表达式或函数(如 'a.b' 或 function() { return vm.a.b })
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      // 简化:只支持函数
      this.getter = expOrFn;
    }

    // 获取初始值,触发依赖收集
    this.value = this.get();
  }

  // 读取值,触发 getter 收集依赖
  get() {
    pushTarget(this); // 设置当前 Watcher 为 Dep.target
    let value;
    try {
      value = this.getter.call(this.vm, this.vm);
    } catch (e) {
      console.error(e);
    } finally {
      popTarget(); // 恢复
      this.cleanupDeps(); // 清理不再依赖的 dep
    }
    return value;
  }

  // 更新
  update() {
    // 这里可以加入异步队列(nextTick),我们简化为同步
    this.run();
  }

  run() {
    const value = this.get(); // 重新获取值
    const oldValue = this.value;
    this.value = value;
    // 执行回调(如更新视图)
    this.cb.call(this.vm, value, oldValue);
  }

  // 添加依赖
  addDep(dep) {
    const id = dep.constructor.name === 'Dep' ? dep : null; // 简化
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  }

  // 清理依赖
  cleanupDeps() {
    let i = this.deps.length;
    while (i--) {
      const dep = this.deps[i];
      if (!this.newDepIds.has(dep)) {
        dep.removeSub(this);
      }
    }
    const tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();

    const depsTmp = this.deps;
    this.deps = this.newDeps;
    this.newDeps = depsTmp;
    this.newDeps.length = 0;
  }
}

// 4. 模拟一个简单的 Vue 实例
class Vue {
  constructor(options) {
    this.$options = options;
    this.$data = options.data;

    // 将 data 代理到 vm 上
    this._proxyData(this.$data);

    // 观察 data
    observe(this.$data);

    // 创建一个渲染 Watcher(模拟视图更新)
    new Watcher(this, () => {
      console.log('✅ 视图更新:', this.$data.message);
      // 这里可以触发虚拟 DOM 重渲染
    }, null, { lazy: false });
  }

  _proxyData(data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key];
        },
        set(newVal) {
          data[key] = newVal;
        }
      });
    });
  }
}