【vue篇】Vue 响应式核心:依赖收集机制深度解密

在 Vue 应用中,你是否好奇:

"当我修改 this.message 时,DOM 为何能自动更新?" "为什么只有被模板用到的数据才会触发更新?" "Vue 是如何知道哪个组件依赖哪个数据的?"

这一切的背后,是 Vue 依赖收集(Dependency Collection) 的精妙设计。

本文将从 Object.definePropertyDep-Watcher 模型,彻底解析 Vue 2 的响应式原理。


一、核心结论:依赖收集 = 数据 ↔ 视图 的双向绑定

text 复制代码
数据变化 → 通知视图更新
     ↑          ↓
   收集      触发 getter
  • 谁收集? Dep(依赖中心)
  • 被谁收集? Watcher(观察者)
  • 何时收集? 组件渲染时读取数据(触发 getter)

二、三大核心角色

🎯 1. defineReactive:让数据"响应式"

js 复制代码
function defineReactive(obj, key, val) {
  // 每个属性都有一个独立的依赖中心
  const dep = new Dep();
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // ✅ 依赖收集:谁在读我?
      if (Dep.target) {
        dep.depend(); // 通知 dep:当前 watcher 依赖我
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      // ✅ 派发更新:通知所有依赖者
      dep.notify();
    }
  });
}

💥 dep 是每个属性的"私人秘书",记录谁依赖它。


🎯 2. Dep:依赖管理中心

js 复制代码
class Dep {
  static target = null; // 🌟 全局唯一,指向当前正在计算的 Watcher
  subs = []; // 存储所有依赖此数据的 Watcher

  // 被收集:当前 Watcher 依赖我
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this); // 告诉 Watcher:你依赖我
    }
  }

  // 添加订阅者
  addSub(watcher) {
    this.subs.push(watcher);
  }

  // 派发更新:数据变了!
  notify() {
    // 避免在 notify 时修改数组
    const subs = this.subs.slice();
    for (let i = 0; i < subs.length; i++) {
      subs[i].update(); // 通知每个 Watcher 更新
    }
  }
}

🔑 Dep.target 是关键:它确保同一时间只有一个 Watcher 在收集依赖。


🎯 3. Watcher:观察者(组件/计算属性)

js 复制代码
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.getter = expOrFn; // 如 vm._update(vm._render())
    this.cb = cb;
    this.deps = [];      // 记录依赖了哪些 dep
    this.depIds = new Set(); // 去重
    this.value = this.get(); // 🚀 首次执行,触发依赖收集
  }

  // 读取数据,触发 getter
  get() {
    pushTarget(this); // 设置当前 Watcher
    const value = this.getter.call(this.vm, this.vm);
    popTarget(); // 清除
    return value;
  }

  // 被 dep 收集
  addDep(dep) {
    const id = dep.id;
    if (!this.depIds.has(id)) {
      this.depIds.add(id);
      this.deps.push(dep);
      dep.addSub(this); // dep 记录我
    }
  }

  // 更新:数据变化后调用
  update() {
    queueWatcher(this); // 异步更新
  }

  run() {
    const value = this.get(); // 重新计算
    this.cb(value, this.value); // 执行回调(如更新 DOM)
    this.value = value;
  }
}

💡 Watcher 是"消费者",它知道自己依赖哪些数据。


三、依赖收集全过程(图文解析)

🔄 阶段 1:初始化响应式数据

js 复制代码
// data: { message: 'Hello' }
defineReactive(data, 'message', 'Hello');
// → 为 message 创建 dep 实例
text 复制代码
data.message
     ↓
   dep (subs: [])

🔄 阶段 2:组件挂载,创建 Watcher

js 复制代码
new Watcher(vm, () => {
  vm._update(vm._render());
});
  • Watcher.get() 被调用;
  • pushTarget(this)Dep.target = watcher
text 复制代码
Dep.target → Watcher实例

🔄 阶段 3:渲染触发 getter,完成收集

js 复制代码
vm._render(); // 生成 VNode
// 模板中:{{ message }}
// → 读取 this.message → 触发 getter
js 复制代码
// getter 执行
get() {
  if (Dep.target) {
    dep.depend(); // dep.depend()
  }
  return val;
}
js 复制代码
// dep.depend()
depend() {
  Dep.target.addDep(this); // watcher.addDep(dep)
}
js 复制代码
// watcher.addDep(dep)
addDep(dep) {
  this.deps.push(dep);
  dep.addSub(this); // dep.subs.push(watcher)
}

收集完成!

text 复制代码
data.message
     ↓
   dep (subs: [watcher])
     ↑
Watcher (deps: [dep])

🔄 阶段 4:数据变化,派发更新

js 复制代码
this.message = 'World'; // 触发 setter
js 复制代码
// setter 执行
set(newVal) {
  val = newVal;
  dep.notify(); // 通知所有 subs
}
js 复制代码
// dep.notify()
notify() {
  this.subs.forEach(watcher => watcher.update());
}

queueWatcher(watcher) → 异步更新 DOM。


四、实战演示:一个简单的响应式系统

js 复制代码
// 1. 数据
const data = { count: 0 };

// 2. 响应式化
defineReactive(data, 'count', 0);

// 3. 创建 Watcher(模拟组件)
new Watcher(null, () => {
  console.log('Render:', data.count);
}, null);
// → 触发 getter → 依赖收集完成

// 4. 修改数据
data.count = 1;
// → 触发 setter → dep.notify() → Watcher.update()
// → 输出:Render: 1

五、Vue 3 的改进:Proxy + effect

js 复制代码
// Vue 3 使用 Proxy
const reactiveData = reactive({ count: 0 });

effect(() => {
  console.log(reactiveData.count); // 收集依赖
});

reactiveData.count++; // 触发更新
  • 优势
    • 支持动态新增属性;
    • 性能更好(无需递归 defineProperty);
    • 代码更简洁。

💡 结语

"依赖收集是 Vue 响应式的灵魂。"

角色 职责
defineReactive 拦截 getter/setter
Dep 管理订阅者(Watcher)
Watcher 观察数据变化,执行更新
过程 关键操作
初始化 defineReactive
收集 Dep.target = watcher + dep.depend()
更新 dep.notify()watcher.update()

掌握依赖收集机制,你就能:

✅ 理解 Vue 响应式原理;

✅ 调试响应式问题;

✅ 设计自己的响应式系统;

✅ 顺利过渡到 Vue 3 的 reactiveeffect

相关推荐
LuckySusu4 小时前
【vue篇】Vue.js 2025:为何全球开发者都在拥抱这个前端框架?
前端·vue.js
LuckySusu4 小时前
【vue篇】Vue 单向数据流铁律:子组件为何不能直接修改父组件数据?
前端·vue.js
LuckySusu4 小时前
【vue篇】React vs Vue:2025 前端双雄终极对比
前端·vue.js
李鸿耀4 小时前
仅用几行 CSS,实现优雅的渐变边框效果
前端
码事漫谈5 小时前
解决 Anki 启动器下载错误的完整指南
前端
im_AMBER5 小时前
Web 开发 27
前端·javascript·笔记·后端·学习·web
蓝胖子的多啦A梦5 小时前
低版本Chrome导致弹框无法滚动的解决方案
前端·css·html·chrome浏览器·版本不同造成问题·弹框页面无法滚动
玩代码5 小时前
vue项目安装chromedriver超时解决办法
前端·javascript·vue.js
訾博ZiBo6 小时前
React 状态管理中的循环更新陷阱与解决方案
前端