用过 Vue 的同学都知道,它最迷人的特性之一,就是数据变化 → 视图自动更新 的响应式魔法。不用手动操作 DOM,只需修改数据,页面就会同步刷新,这背后到底藏着怎样的逻辑?
其实答案很简单:Vue2 响应式的本质,就是 数据劫持 + 发布订阅模式 的结合。但这两个概念听起来抽象,具体是如何协同工作、实现“数据驱动视图”的?今天我们就用 50 行极简代码,拆解它的核心实现,让你一看就懂、一写就会。
一、先搞懂三个核心角色
Vue2 响应式就像一个通知系统,只有三个角色:
- Dep(调度中心) 负责记录谁用到了数据,数据一变就群发通知。
- Watcher(观察者) 用到数据的地方(视图 / 渲染函数),收到通知就更新。
- defineReactive(数据劫持) 给数据装上 “监控”,监听读取和修改行为。
一句话总结:
读数据 → 收集依赖
改数据 → 派发更新
二、代码实现迷你 Vue2 响应式
数据劫持 + 依赖收集 + 派发更新
// ====================
// 1. 调度中心 Dep:负责收集、通知 watcher
// 发布订阅模式的核心
// ====================
class Dep {
constructor() {
this.subs = []; // 存放所有 watcher(订阅者名单)
}
// 收集依赖:把当前 watcher 加入名单
addSub(watcher) {
this.subs.push(watcher);
}
// 通知更新:数据变了,告诉所有 watcher 更新
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
// 全局唯一标记:当前正在读取数据的 watcher
Dep.target = null;
// ====================
// 2. 观察者 Watcher:用到数据的地方(视图/渲染)
// ====================
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn; // 保存视图更新函数
this.get(); // 创建时立即执行一次 → 首次渲染 + 依赖收集
}
get() {
Dep.target = this; // 标记:现在是我在用数据
this.updateFn(); // 执行渲染 → 触发数据的 get
Dep.target = null; // 收集完毕,清空标记
}
// 收到 Dep 通知,执行更新
update() {
console.log("🔄 视图更新啦!");
this.updateFn(); // 重新渲染
}
}
// ====================
// 3. 数据劫持:把普通对象变成响应式
// 使用 Object.defineProperty 监听 get/set
// ====================
function defineReactive(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key]; // 闭包保存属性值
const dep = new Dep(); // 每个属性,单独配一个调度中心
// 劫持属性的 读 和 写
Object.defineProperty(obj, key, {
get() {
// 依赖收集:谁在读我,就把谁加入订阅名单
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
set(newVal) {
if (newVal === value) return; // 值没变化,不处理
value = newVal; // 更新值
// 派发更新:通知所有 watcher 重新渲染
dep.notify();
}
});
});
}
// ====================
// 4. 模拟 Vue 使用
// ====================
const data = {
msg: "Hello Vue"
};
// 把 data 变成响应式数据
defineReactive(data);
// 创建 Watcher 模拟页面渲染 {{ msg }}
new Watcher(() => {
console.log("渲染视图:", data.msg);
});
三、代码解析
第一步:数据变成响应式(数据劫持)
执行 defineReactive(data),给 data.msg 装上 get 和 set,并创建一个 Dep 实例。
- 用
Object.defineProperty - 给对象的每个属性 劫持 get /set
- 让数据变成 “可监听、可感知”
这一步就是 数据劫持。
第二步:创建 Watcher → 首次渲染 + 依赖收集
- 执行
get() Dep.target = 当前watcher- 执行渲染函数 → 读取 data.msg
- 触发
get→ 把 watcher 存入 dep - 输出:渲染视图:Hello Vue
- 清空标记
这一步叫:依赖收集
第三步:修改数据 → 自动更新视图
data.msg = "我学会响应式啦!"
触发流程:
- 触发
set - 更新数据
- 执行
dep.notify() - 通知所有 watcher 执行
update() - 重新渲染视图
这一步叫:派发更新
最终效果
只修改数据,视图自动更新! 这就是 Vue 响应式的魔法!
四、总结
-
数据劫持:使用
Object.defineProperty监听对象的get和set。 -
依赖收集:渲染时读取数据 → 触发
get→ 把 Watcher 存入 Dep。 -
派发更新:修改数据 → 触发
set→ Dep 通知所有 Watcher 更新。 -
设计模式:发布 — 订阅模式。