Vue双向绑定原理
Vue 的双向绑定(Two-Way Data Binding)主要依赖于 数据劫持(Reactive System)+ 依赖收集(Dep & Watcher) ,核心在于 数据变化时自动更新视图,以及 视图变化时同步更新数据。
Vue 2 双向绑定原理
Vue 2 使用 Object.defineProperty() 进行数据劫持,并结合 Watcher 和 Dep 进行依赖收集。
1. 数据劫持(Observer)
Vue 2 在 data 初始化时,会遍历对象的每个属性,并使用 Object.defineProperty() 进行劫持。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}`);
return val;
},
set(newVal) {
console.log(`修改 ${key} = ${newVal}`);
val = newVal;
}
});
}
const data = {};
defineReactive(data, "message", "Hello Vue");
console.log(data.message); // 读取 message
data.message = "Hello World"; // 修改 message = Hello World
关键点:
- 使用
Object.defineProperty()劫持对象的get/set方法。 - 当
get被调用时,触发依赖收集。 - 当
set被调用时,触发视图更新。
2. 依赖收集(Dep & Watcher)
Vue 2 通过 Dep 和 Watcher 进行依赖收集,使得数据变更后可以通知相关的视图更新。
(1)Dep:依赖管理
Dep 维护一个 subs 依赖列表,每个属性对应一个 Dep,用于收集依赖。
class Dep {
constructor() {
this.subs = []; // 依赖列表
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
(2)Watcher:订阅者
Watcher 订阅 Dep,当数据变化时,它会收到通知并更新视图。
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
}
update() {
this.updateFn();
}
}
(3)绑定 Dep 和 Watcher
当 属性被读取(get)时,收集 Watcher;当 属性被修改(set)时,通知 Watcher 更新视图。
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
dep.addSub(Dep.target); // 依赖收集
return val;
},
set(newVal) {
val = newVal;
dep.notify(); // 触发更新
}
});
}
const data = {};
defineReactive(data, "message", "Hello Vue");
// 创建 Watcher 监听数据变化
const watcher = new Watcher(() => {
console.log("视图更新:" + data.message);
});
Dep.target = watcher; // 模拟触发依赖收集
console.log(data.message);
Dep.target = null;
// 修改数据,触发更新
data.message = "Hello World";
总结 Vue 2 原理:
Object.defineProperty()劫持数据get/setDep依赖收集Watcher监听数据变化,通知视图更新
Vue 3 双向绑定原理
Vue 3 使用 Proxy 取代 Object.defineProperty(),使得 可以监听整个对象,不仅限于某个属性。
1. reactive() 代理数据
Vue 3 的 reactive() 通过 Proxy 劫持整个对象,返回一个可响应的代理。
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log(`读取 ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`修改 ${key} = ${value}`);
return Reflect.set(target, key, value, receiver);
}
});
}
const state = reactive({ count: 0 });
console.log(state.count); // 读取 count
state.count = 1; // 修改 count = 1
2. 依赖收集 & 触发更新
Vue 3 依然使用 Dep 和 Watcher,但收集依赖的方式不同。
const bucket = new WeakMap();
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
if (!bucket.has(target)) {
bucket.set(target, new Map());
}
const deps = bucket.get(target);
if (!deps.has(key)) {
deps.set(key, new Set());
}
deps.get(key).add(activeEffect); // 依赖收集
return target[key];
},
set(target, key, value) {
target[key] = value;
if (bucket.has(target) && bucket.get(target).has(key)) {
bucket.get(target).get(key).forEach(effect => effect());
}
return true;
}
});
}
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn(); // 立即执行一次,触发依赖收集
activeEffect = null;
}
// 使用示例
const state = reactive({ count: 0 });
effect(() => {
console.log("视图更新:" + state.count);
});
state.count = 1; // 触发 effect 更新
Vue 3 主要优化点
Proxy能监听整个对象,支持数组、新增属性等,Vue 2 需要Vue.set()手动监听新属性。- 依赖收集存储在
WeakMap,避免内存泄漏。 - 响应式系统更灵活,支持
shallowReactive()、readonly()等不同模式。
Vue 2 和 Vue 3 对比
| Vue 2 | Vue 3 | |
|---|---|---|
| 核心 API | Object.defineProperty() | Proxy |
| 依赖收集方式 | Dep + Watcher | WeakMap 存储依赖 |
| 响应式支持 | 只能监听已存在的属性 | 能监听新增/删除属性 |
| 性能 | 遍历所有属性进行 defineProperty | Proxy 直接代理整个对象 |
| 代码简洁性 | 代码较复杂 | 代码更简洁,性能更优 |
总结
Vue 双向绑定核心在于 数据劫持 + 依赖收集 + 视图更新:
- Vue 2 使用
Object.defineProperty(),每个属性单独劫持。 - Vue 3 使用
Proxy,直接代理整个对象,更灵活高效。 Dep和Watcher负责依赖收集,实现数据变更触发视图更新。
Vue 3 的响应式系统更强大,避免了 Vue 2 的性能瓶颈,使得响应式更自然、更高效!🚀