vue实现双向绑定
Vue 双向绑定实现原理
Vue 的双向绑定是通过数据劫持结合发布者-订阅者模式实现的,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。
Vue 2.x 实现方式
数据劫持
通过 Object.defineProperty 劫持对象的属性访问和修改:
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newVal) {
if (newVal !== value) {
value = newVal;
dep.notify(); // 通知订阅者更新
}
}
});
依赖收集
每个被劫持的属性都有一个 Dep 实例,用于管理所有订阅者(Watcher):
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
模板编译
Vue 编译器将模板解析为渲染函数,在解析过程中遇到指令(如 v-model)时会创建对应的 Watcher:

class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get() {
Dep.target = this;
const value = this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
update() {
this.run();
}
run() {
const value = this.get();
if (value !== this.value) {
const oldValue = this.value;
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
}
}
Vue 3.x 实现方式
Vue 3 改用 Proxy 实现数据劫持,解决了 Vue 2 中无法检测数组和对象新增属性的限制:
const handler = {
get(target, key) {
track(target, key); // 依赖收集
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
trigger(target, key); // 触发更新
return result;
}
};
const observed = new Proxy(raw, handler);
实现双向绑定的示例
Vue 2.x 风格实现

function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
if (Dep.target) dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify();
}
});
}
Vue 3.x 风格实现
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
trigger(target, key);
return result;
}
});
}
在组件中使用双向绑定
通过 v-model 指令实现表单元素的双向绑定:
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
对于自定义组件,需要通过 model 选项配置:
Vue.component('custom-input', {
props: ['value'],
model: {
prop: 'value',
event: 'input'
},
template: `
<input
:value="value"
@input="$emit('input', $event.target.value)"
>
`
});





