推荐答案
Vue 的数据响应式原理是通过 Object.defineProperty 或 Proxy 来实现的。Vue 在初始化时会对 data 对象中的属性进行递归遍历,使用 Object.defineProperty 将这些属性转换为 getter 和 setter。当数据发生变化时,setter 会通知依赖该数据的视图进行更新。
本题详细解读
1. 数据劫持
Vue 在初始化时会对 data 对象进行递归遍历,使用 Object.defineProperty 将每个属性转换为 getter 和 setter。这样,当访问或修改这些属性时,Vue 能够捕获到这些操作。
// www.javascriptcn.com code example
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log(`get ${key}: ${val}`);
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) return;
console.log(`set ${key}: ${newVal}`);
val = newVal;
// 触发视图更新
}
});
}2. 依赖收集
Vue 在模板编译过程中会解析模板中的指令和插值表达式,生成渲染函数。在渲染函数执行时,会访问 data 中的属性,触发 getter,此时 Vue 会将当前的 Watcher 实例添加到依赖列表中。
// www.javascriptcn.com code example
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get() {
Dep.target = this;
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);
}
}3. 派发更新
当 data 中的属性被修改时,会触发 setter,setter 会通知所有依赖该属性的 Watcher 实例,Watcher 实例会调用 update 方法,最终触发视图的重新渲染。
// www.javascriptcn.com code example
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}4. Vue 3 中的 Proxy
在 Vue 3 中,数据响应式系统使用了 Proxy 来代替 Object.defineProperty。Proxy 可以监听整个对象的变化,而不需要递归遍历每个属性,性能更好。
// www.javascriptcn.com code example
const handler = {
get(target, key, receiver) {
console.log(`get ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`set ${key}: ${value}`);
return Reflect.set(target, key, value, receiver);
}
};
const observed = new Proxy({}, handler);通过以上机制,Vue 实现了数据的响应式,使得数据的变化能够自动反映到视图上。