Vue.js 的核心魅力之一,就是它的 响应式系统(Reactive System)。
你只需修改数据,视图便会自动更新。这背后究竟是如何实现的?
本文将深入剖析 Vue 2 和 Vue 3 的响应式原理,带你理解 data
是如何"活"起来的。
一、Vue 2 的响应式原理:Object.defineProperty
✅ 核心流程图
objectivec
data → defineProperty(getter/setter) → 依赖收集 → 派发更新 → 视图刷新
✅ 1. 初始化阶段:数据劫持
当创建 Vue 实例时,Vue 会遍历 data
中的所有属性,使用 Object.defineProperty
将它们转换为 getter/setter。
js
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
observe(val);
const dep = new Dep(); // 依赖收集器
Object.defineProperty(obj, key, {
get() {
// 依赖收集:谁在读取这个值?
if (Dep.target) {
dep.depend();
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 新值也可能是对象,需要继续劫持
observe(newVal);
// 派发更新:通知所有依赖更新
dep.notify();
}
});
}
✅ 2. 依赖收集(Dependency Collection)
- 每个响应式数据都有一个 依赖收集器(Dep);
- 当组件渲染时,会读取
data
中的属性(触发getter
); - 此时,
Dep
会将当前正在渲染的 Watcher 记录为依赖。
js
class Dep {
constructor() {
this.subs = []; // 存储所有依赖(Watcher)
}
depend() {
if (Dep.target) {
this.subs.push(Dep.target);
}
}
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
Dep.target
是当前正在执行的 Watcher(通过栈管理)。
✅ 3. 派发更新(Notify Update)
- 当数据被修改时,触发
setter
; setter
调用dep.notify()
;- 所有依赖该数据的 Watcher 收到通知,执行
update()
。
✅ 4. Watcher:连接数据与视图的桥梁
每个组件实例对应一个 Watcher 实例。
js
class Watcher {
constructor(vm, exprOrFn, cb) {
this.vm = vm;
this.getter = exprOrFn; // 例如:render function
this.cb = cb;
this.value = this.get(); // 首次执行,触发 getter,完成依赖收集
}
get() {
Dep.target = this; // 设置当前 Watcher
const value = this.getter.call(this.vm, this.vm);
Dep.target = null; // 清除
return value;
}
update() {
const oldValue = this.value;
this.value = this.get(); // 重新执行,获取新值
this.cb.call(this.vm, this.value, oldValue);
}
}
✅ 完整流程演示
js
new Vue({
data: {
message: 'Hello Vue'
},
template: `<div>{{ message }}</div>`
});
-
初始化 :
message
被defineProperty
劫持; -
首次渲染 :
- 执行
render
函数,读取message
; - 触发
getter
,Dep
收集当前组件的Watcher
;
- 执行
-
数据更新 :
jsvm.message = 'Hello World';
- 触发
setter
; dep.notify()
通知Watcher
;Watcher
重新执行render
,视图更新。
- 触发
二、Vue 3 的响应式原理:Proxy
❌ Vue 2 的局限性
- 无法检测属性的添加或删除;
- 数组方法(如
push
、splice
)需要特殊处理; - 初始化时需要递归遍历
data
,性能开销大。
✅ Vue 3 的解决方案:Proxy
js
const reactive = (obj) => {
return new Proxy(obj, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 派发更新
trigger(target, key);
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
trigger(target, key);
return result;
}
});
};
✅ Vue 3 的优势
特性 | Vue 2 (defineProperty ) |
Vue 3 (Proxy ) |
---|---|---|
检测属性新增/删除 | ❌ 需 vm.$set |
✅ 原生支持 |
检测数组索引修改 | ❌ 特殊处理 | ✅ 原生支持 |
性能 | 初始化递归劫持 | 惰性劫持(访问时才代理) |
兼容性 | IE9+ | IE 不支持 |
三、响应式系统的核心模块
模块 | 作用 |
---|---|
Observer | 遍历 data ,递归添加响应式 |
Dep | 依赖收集器,管理 Watcher 列表 |
Watcher | 观察者,连接数据与视图 |
Compiler | 编译模板,生成 render 函数(Vue 2) |
在 Vue 3 中,
Dep
和Watcher
被effect
和track/trigger
取代,逻辑更清晰。
四、常见问题与注意事项
❌ Vue 2 中的注意事项
js
// 1. 属性新增
vm.obj.newProp = 'hi'; // ❌ 不会触发更新
this.$set(vm.obj, 'newProp', 'hi'); // ✅
// 2. 数组索引修改
vm.items[0] = 'new'; // ❌
vm.$set(vm.items, 0, 'new'); // ✅
// 或使用 splice
vm.items.splice(0, 1, 'new');
// 3. 删除属性
delete vm.obj.prop; // ❌
this.$delete(vm.obj, 'prop'); // ✅
✅ Vue 3 中的改进
js
const state = reactive({ count: 0 });
// 所有操作天然响应式
state.count++;
state.newProp = 'hi';
delete state.count;
💡 结语
"Vue 的响应式系统,是数据驱动视图的魔法引擎。"
- Vue 2 :基于
Object.defineProperty
,通过Dep-Watcher
模式实现; - Vue 3 :基于
Proxy
,更强大、更高效。
理解响应式原理,不仅能帮助你:
- 避免常见响应式陷阱;
- 写出更高效的代码;
- 在面试中脱颖而出。