Vue 的响应式系统是其核心特性之一,主要依赖数据劫持结合发布-订阅模式来实现数据变化时视图的自动更新
Vue 2:基于 Object.defineProperty
- 数据劫持(响应式化)
Vue 在初始化时会遍历 data 中的属性,使用 Object.defineProperty 将它们转换为 getter/setter,从而拦截对数据的读取和修改。 - 依赖收集(Dependency Collection)
在 getter 中,会将当前的 Watcher(依赖)添加到 Dep(依赖管理器)中。
每个响应式属性都有一个对应的 Dep 实例,用于存储所有依赖于该属性的 Watcher。 - 派发更新(Dependency Notification)
当数据被修改时,setter 会被触发,并通知 Dep 中所有的 Watcher 进行更新。
Watcher 会执行其回调(例如更新视图、计算属性等),完成响应式更新。 - 数组的处理
Vue 2 对数组的方法(如 push、pop、splice 等)进行了重写,使得通过这些方法修改数组也能触发视图更新。
// 简化版响应式实现
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher);
}
}
notify() {
this.subscribers.forEach(watcher => watcher.update());
}
}
let activeWatcher = null;
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
this.update();
}
update() {
activeWatcher = this;
this.updateFn();
activeWatcher = null;
}
}
function observe(data) {
if (typeof data !== 'object' || data === null) return;
Object.keys(data).forEach(key => {
let value = data[key];
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
dep.depend();
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
dep.notify();
}
}
});
observe(value);
});
}
// 使用示例
const data = { count: 0, name: 'Vue' };
observe(data);
new Watcher(() => {
console.log(`Count changed: ${data.count}`);
});
data.count = 1; // 输出: Count changed: 1
局限:
无法检测到对象属性的添加和删除(需用 Vue.set/Vue.delete)
数组变异方法需重写(push、pop 等),通过拦截器实现
初始化时递归遍历整个对象,性能有损耗
Vue 3:基于 Proxy
- 更强大的拦截
Proxy 可以直接拦截整个对象,包括属性的增删、数组索引修改、length 变化等,无需递归遍历所有属性。 - 响应式核心
reactive():返回一个对象的 Proxy 代理,拦截所有操作。
ref():将基本类型包装为 { value: ... }对象,并通过 getter/setter 实现响应式。 - 依赖收集与触发
通过 track()在 get 操作时收集依赖(存储到全局的 targetMap)。
通过 trigger()在 set 操作时触发更新。
// 简化版 Proxy 实现
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
const targetMap = new WeakMap();
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
trigger(target, key);
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
trigger(target, key);
return result;
}
});
}
// 使用示例
const state = reactive({ count: 0, name: 'Vue3' });
effect(() => {
console.log(`Count: ${state.count}`);
});
state.count = 1; // 输出: Count: 1
state.newProp = '动态添加'; // 也能触发响应式
评论区