深入理解 Vue3 数据绑定实现原理 🔗
本文从响应式系统的核心出发,剖析 Vue3 如何基于 Proxy 实现数据绑定,对比 Vue2 的 Object.defineProperty,并详解 ref、reactive、effect 等 API 的底层机制,帮助读者建立完整的响应式心智模型。
前言
Vue3 的响应式系统相比 Vue2 进行了全面重构,从 Object.defineProperty 升级为 Proxy,带来了更好的性能与更完善的功能。理解其数据绑定原理,不仅能帮助我们写出更高效的 Vue 代码,也是深入前端框架设计的必经之路。本文将带你从零理解 Vue3 响应式的实现思路。
一、Vue2 与 Vue3 响应式对比
1.1 Vue2:Object.defineProperty 的局限
Vue2 使用 Object.defineProperty 劫持对象的 getter/setter:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
console.log('set', key);
}
}
});
}
主要局限:
- 无法监听数组索引和
length变化,需对数组方法做 hack - 无法监听对象属性的新增和删除
- 必须递归遍历对象,初始化成本高
1.2 Vue3:Proxy 的优势
Vue3 使用 Proxy 代理整个对象,可拦截 13 种操作:
const obj = { a: 1 };
const proxy = new Proxy(obj, {
get(target, key) {
console.log('get', key);
return Reflect.get(target, key);
},
set(target, key, value) {
console.log('set', key);
return Reflect.set(target, key, value);
}
});
proxy.a; // get a
proxy.b = 2; // set b,可监听新增属性
优势:支持数组、支持新增/删除属性、性能更好、代码更简洁。
二、reactive:基于 Proxy 的响应式对象
Vue3 的 reactive() 返回一个 Proxy 包装的对象:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key); // 依赖收集
if (typeof res === 'object' && res !== null) {
return reactive(res); // 懒代理:访问时才递归
}
return res;
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return res;
}
});
}
懒代理:只有访问到的嵌套对象才会被代理,避免一次性递归整个对象,提升性能。
三、ref:基本类型的响应式包装
ref 用于包装基本类型(number、string、boolean 等),因为 Proxy 只能代理对象:
function ref(value) {
const refObj = {
get value() {
track(refObj, 'value');
return value;
},
set value(newVal) {
if (newVal !== value) {
value = newVal;
trigger(refObj, 'value');
}
}
};
return refObj;
}
在模板中使用 ref 时会自动解包,无需 .value;在 <script setup> 中需要 .value 访问。
四、依赖收集与派发更新
4.1 核心数据结构
const targetMap = new WeakMap(); // target -> key -> Set<effect>
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set()));
dep.add(activeEffect); // 当前正在执行的 effect
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
dep?.forEach(effect => effect());
}
4.2 effect:副作用函数
effect 会在依赖的响应式数据变化时重新执行:
let activeEffect;
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
// 使用
const state = reactive({ count: 0 });
effect(() => {
console.log(state.count); // 自动收集 count 的依赖
});
state.count++; // 触发 effect 重新执行
五、computed 与 watch 的简化实现
5.1 computed
computed 本质是带缓存的 effect:
function computed(getter) {
let value;
let dirty = true;
const effectFn = effect(getter, {
lazy: true,
scheduler() {
dirty = true;
trigger(obj, 'value');
}
});
const obj = {
get value() {
if (dirty) {
value = effectFn();
dirty = false;
}
track(obj, 'value');
return value;
}
};
return obj;
}
5.2 watch
watch 基于 effect + 深度比较,当源变化时执行回调。
六、总结
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 实现方式 | Object.defineProperty | Proxy |
| 数组监听 | 需 hack 方法 | 原生支持 |
| 新增/删除属性 | 需 Vue.set | 原生支持 |
| 嵌套代理 | 初始化时递归 | 懒代理,按需 |
| 性能 | 一般 | 更优 |
Vue3 的响应式系统通过 Proxy + 依赖收集(track/trigger)+ 副作用(effect) 三件套,实现了高效、完整的数据绑定。理解这套机制,有助于我们正确使用 reactive、ref、computed,并避免常见的响应式陷阱(如解构丢失响应性)。