vue 双向绑定实现
Vue 双向绑定的实现原理
Vue 的双向绑定主要通过数据劫持结合发布-订阅模式实现,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。以下是具体实现方式:
数据劫持(响应式系统)
通过 Object.defineProperty 或 Proxy 拦截数据的读写操作,在数据变化时触发更新。
// Vue 2.x 使用 Object.defineProperty
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
// 触发更新
dep.notify();
}
}
});
}
// Vue 3.x 使用 Proxy
const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 触发更新
trigger(target, key);
return result;
}
});
};
依赖收集(发布-订阅模式)
每个响应式数据维护一个依赖收集器(Dep),用于存储所有依赖该数据的 Watcher 实例。

class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
class Watcher {
constructor(vm, exp, fn) {
this.vm = vm;
this.exp = exp;
this.fn = fn;
Dep.target = this;
// 触发 getter 以收集依赖
this.vm[this.exp];
Dep.target = null;
}
update() {
this.fn();
}
}
模板编译
Vue 通过编译器将模板解析为渲染函数,在解析过程中识别指令(如 v-model)并生成对应的数据绑定代码。
// 简化的 v-model 编译逻辑
function compileModel(el, dir, _warn) {
const { value, modifiers } = dir;
// 生成代码,将输入事件与数据更新绑定
addHandler(el, 'input', `$event => { ${value} = $event.target.value }`);
}
双向绑定实现(v-model)
v-model 是语法糖,本质是 :value 和 @input 的结合:

<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
对于组件,v-model 默认使用 value prop 和 input 事件,可通过 model 选项自定义:
// 组件内
export default {
model: {
prop: 'checked',
event: 'change'
},
props: ['checked'],
methods: {
handleChange(e) {
this.$emit('change', e.target.value);
}
}
};
实现简易双向绑定
以下是一个简化版的实现示例:
class Vue {
constructor(options) {
this.$data = options.data;
this.observe(this.$data);
this.compile(options.el);
}
observe(data) {
Object.keys(data).forEach(key => {
let value = data[key];
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
Dep.target && dep.addSub(Dep.target);
return value;
},
set(newVal) {
if (newVal !== value) {
value = newVal;
dep.notify();
}
}
});
});
}
compile(el) {
const element = document.querySelector(el);
Array.from(element.children).forEach(node => {
if (node.hasAttribute('v-model')) {
const key = node.getAttribute('v-model');
node.value = this.$data[key];
new Watcher(this, key, () => {
node.value = this.$data[key];
});
node.addEventListener('input', () => {
this.$data[key] = node.value;
});
}
});
}
}






