响应式原理
Vue2 是采用数据劫持配合发布者-订阅者模式的方式实现响应式的。通过Object.defineProperty 来劫持各个属性的 setter 和 getter,在数据变动时,发布消息给依赖收集器 Dep 去通知观察者 Watcher,并由 Watcher 执行更新视图的回调函数。
Vue3 使用 Proxy 取代 Object.defineProperty 的原因:
1)无法监听新增属性和删除属性 :使用Object.defineProperty可以监听已有属性的变化,但是它无法监听新增属性和删除属性。2)无法监听数组下标的变化 :使用Object.defineProperty监听数组时,只能监听到数组元素的值的变化,而无法监听数组下标的变化。
响应式原理图
实现代码
// 数据劫持
function defineReactive(data, key, value) {
let dep = new Dep();
// defineProperty 数据劫持
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
// 通知收集 - addWatcher()
get() {
console.log('getter--', value);
if (Dep.target) {
dep.addWatcher(Dep.target);
}
return value;
},
// 通知变化 - notify()
set(newValue) {
if (newValue !== value) {
value = newValue;
dep.notify();
}
},
});
}
// Watcher 监听器
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
// 将watcher赋值给Dep类的静态属性target
Dep.target = this;
// 触发一次getter,进行依赖收集
console.log(this.vm.data[this.key],'---value');
// 重置Dep类的静态属性target
Dep.target = null;
}
// 通过回调函数更新视图
update() {
this.callback.call(this.vm, this.vm.data[this.key]);
}
}
// 收集订阅器
class Dep {
constructor() {
this.watchers = [];
}
// 添加订阅者
addWatcher(watcher) {
this.watchers.push(watcher);
}
// 通知更新
notify() {
this.watchers.forEach((watcher) => {
watcher.update();
});
}
}
class Vue {
constructor(options) {
this.data = options.data;
console.log(this.data, '--1');
// 实现数据劫持
Object.keys(this.data).forEach((key) => {
defineReactive(this.data, key, this.data[key]);
});
// 创建Watcher对象,监听数据变化
new Watcher(this, 'message', this.updateMessage);
// 更新视图
setTimeout(() => {
this.data.message = 'Hello, Vue!';
console.log(this.data, '--2');
}, 2000);
}
// 数据变化时的回调函数
updateMessage(newValue) {
console.log('Updated message:', newValue);
}
}
// 测试
const vm = new Vue({
data: {
message: 'Hello, World!',
},
});
// 输出
/*
{message: 'Hello, World!'} '--1'
getter-- Hello, World!
Hello, World! ---value
getter-- Hello, Vue!
Updated message: Hello, Vue!
{message: 'Hello, Vue!'} '--2'
*/