Vue 的响应式原理和双向绑定实现,核心在于利用数据劫持(通过 Object.defineProperty 或 Proxy)来实现数据和视图的同步更新,同时通过依赖追踪和发布-订阅模式来实现变化通知。
一、双向绑定原理
双向绑定可以理解为两部分:
- 数据驱动视图(数据到视图):
- Vue通过响应式系统来监听数据的变化,当数据发生改变时,自动更新相关的视图。这是通过
getter/setter提前设置数据变化的回调函数来完成的。
- Vue通过响应式系统来监听数据的变化,当数据发生改变时,自动更新相关的视图。这是通过
- 视图驱动数据(视图到数据):
- 通过用户与视图的交互(如表单输入等),触发事件监听器,自动将视图的变化反馈到数据上。
- Vue 的
v-model是最典型的双向绑定实现,表单元素的变化通过事件监听自动同步到数据。
二、响应式原理
Vue 的响应式系统是通过以下几个步骤实现的:
-
数据劫持(Observer 模式):
- Vue 使用
Observer对象来对数据进行劫持。它会递归遍历整个数据对象的所有属性,并使用Object.defineProperty将每个属性转换为 getter/setter。在 Vue3 中则是通过Proxy进行劫持。 getter用来收集依赖,setter用来触发更新。
- Vue 使用
-
依赖追踪(Dep 模式):
- Vue 会在数据的
getter中收集依赖,这个依赖关系是通过Dep来管理的。每个数据属性都会创建一个对应的Dep对象,用来存储所有依赖这个属性的Watcher对象。
- Vue 会在数据的
-
依赖收集(Watcher 模式):
- 当视图中访问响应式数据时,
getter方法会触发,将当前视图的Watcher对象添加到这个属性的Dep对象中。这样,当数据变化时,Vue 就可以找到依赖此数据的所有Watcher,并通知它们更新。
- 当视图中访问响应式数据时,
-
响应更新:
- 当响应式数据被修改时,Vue 会触发
setter方法,通知对应的Dep对象,Dep会通知所有依赖这个数据的Watcher,Watcher接收到通知后,会更新视图。
- 当响应式数据被修改时,Vue 会触发
三、核心代码示例(Vue2 实现)
// 1. 数据劫持:Observer 类
class Observer {
constructor(data) {
this.observe(data);
}
observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
});
}
defineReactive(obj, key, value) {
this.observe(value); // 递归遍历对象
const dep = new Dep(); // 为每个属性实例化依赖管理器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
Dep.target && dep.addDep(Dep.target);
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
dep.notify(); // 通知依赖更新
}
}
});
}
}
// 2. 依赖管理:Dep 类
class Dep {
constructor() {
this.subs = []; // 存放依赖
}
addDep(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
// 3. Watcher 类,负责更新视图
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this; // 将当前的 watcher 实例挂载到 Dep.target 上
this.vm[key]; // 触发 getter 收集依赖
Dep.target = null; // 收集完依赖后清除
}
update() {
// 数据更新时,调用回调更新视图
this.cb.call(this.vm, this.vm[this.key]);
}
}
// 4. Vue 模拟实现
class Vue {
constructor(options) {
this.$data = options.data;
this.observe(this.$data); // 实现数据劫持
new Watcher(this, 'message', function(newVal) {
console.log('视图更新了:', newVal);
});
}
observe(data) {
new Observer(data); // 将数据变成响应式的
}
}
// 测试
const vm = new Vue({
data: {
message: 'Hello, Vue!'
}
});
vm.$data.message = 'Hello, World!'; // 修改数据后,视图会自动更新
四、Vue3 响应式系统的变化
在 Vue3 中,Vue 使用 Proxy 替代了 Object.defineProperty 来实现响应式系统。相比于 Vue2 的数据劫持,Proxy 可以直接监听对象新增或删除的属性,并且支持数组等复杂数据类型的直接操作。
Vue3 响应式系统简单实现:
const reactive = (target) => {
if (typeof target !== 'object' || target === null) return target;
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
console.log(`获取属性:${key} = ${result}`);
return result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
console.log(`设置属性:${key} = ${value}`);
return result;
}
};
return new Proxy(target, handler);
};
// 测试
const state = reactive({ message: 'Hello, Vue3!' });
state.message = 'Hello, Proxy!';