vue数据双向绑定实现
Vue 数据双向绑定的实现原理
Vue 的双向数据绑定主要通过 数据劫持 和 发布-订阅模式 实现,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。以下是具体实现方式:
基于 Object.defineProperty 的实现(Vue 2.x)
数据劫持
通过 Object.defineProperty 劫持对象的属性,在属性被访问或修改时触发 getter 和 setter:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}: ${val}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`设置 ${key}: ${newVal}`);
val = newVal;
// 触发更新(模拟依赖通知)
dep.notify();
}
},
});
}
依赖收集与发布订阅
每个属性维护一个依赖列表(Dep),在 getter 中收集依赖(Watcher),在 setter 中通知更新:
class Dep {
constructor() {
this.subscribers = [];
}
addSub(watcher) {
this.subscribers.push(watcher);
}
notify() {
this.subscribers.forEach(watcher => watcher.update());
}
}
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
Dep.target = this; // 标记当前 Watcher
this.value = vm[key]; // 触发 getter 以收集依赖
Dep.target = null;
}
update() {
this.value = this.vm[this.key];
this.callback(this.value);
}
}
实现 Observer
递归遍历对象的所有属性,将其转换为响应式:
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return;
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
observe(obj[key]); // 深度劫持
});
}
基于 Proxy 的实现(Vue 3.x)
Vue 3.x 使用 Proxy 替代 Object.defineProperty,解决了后者无法监听数组变化和动态新增属性的问题:

function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return true;
},
});
}
依赖收集与触发
通过全局的 effect 和 track/trigger 实现:
let activeEffect;
function effect(fn) {
activeEffect = fn;
fn(); // 执行时会触发 getter
activeEffect = null;
}
const targetMap = new WeakMap();
function track(target, key) {
if (!activeEffect) return;
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);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
dep && dep.forEach(effect => effect());
}
双向绑定的实际应用
在 Vue 模板中,v-model 是语法糖,结合了 :value 和 @input:
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
自定义组件实现
在组件中实现 v-model 需定义 modelValue 和 update:modelValue:

export default {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`,
};
关键区别与优化
-
Proxy 的优势
- 直接监听对象而非属性,支持动态新增属性。
- 无需递归遍历对象,性能更高。
- 原生支持数组变化监听。
-
性能优化
- Vue 3.x 的响应式系统通过
Proxy和WeakMap减少内存占用。 - 依赖收集的粒度更细,避免不必要的更新。
- Vue 3.x 的响应式系统通过
-
局限性
Object.defineProperty无法检测到对象属性的添加或删除(需配合Vue.set/Vue.delete)。Proxy不兼容 IE11,需 polyfill 支持。






