vue双向绑定实现简单
Vue 双向绑定的实现原理
Vue 的双向绑定是通过 v-model 指令实现的,其核心是基于数据劫持和发布-订阅模式。以下是一个简化版的实现示例:
数据劫持(响应式系统)
利用 Object.defineProperty 或 ES6 的 Proxy 监听数据变化:
function observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
});
}
function defineReactive(obj, key, val) {
observe(val); // 递归监听嵌套属性
const dep = new Dep(); // 每个属性维护一个依赖管理器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target); // 收集依赖
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify(); // 通知更新
}
});
}
依赖管理(发布-订阅模式)
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Dep.target = null; // 静态属性指向当前 Watcher 实例
观察者(Watcher)
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 触发属性 getter 收集依赖
}
get() {
Dep.target = this;
const value = this.vm[this.exp]; // 触发 getter
Dep.target = null;
return value;
}
update() {
const newValue = this.vm[this.exp];
if (this.value !== newValue) {
this.value = newValue;
this.cb(newValue);
}
}
}
实现 v-model 指令
class Vue {
constructor(options) {
this.$data = options.data;
observe(this.$data);
this.$compile(options.el);
}
$compile(el) {
const element = document.querySelector(el);
this.compileElement(element);
}
compileElement(node) {
Array.from(node.attributes).forEach(attr => {
if (attr.name === 'v-model') {
const exp = attr.value;
node.addEventListener('input', e => {
this.$data[exp] = e.target.value; // 触发 setter
});
new Watcher(this, exp, value => {
node.value = value; // 更新视图
});
}
});
// 递归处理子节点
node.childNodes.forEach(child => {
if (child.nodeType === 1) this.compileElement(child);
});
}
}
使用示例
<div id="app">
<input v-model="message">
<p>{{ message }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
关键点说明
- 数据劫持:通过
Object.defineProperty将数据转为响应式 - 依赖收集:在 getter 中收集依赖的 Watcher
- 派发更新:在 setter 中通知所有 Watcher 更新视图
- 指令解析:编译模板时处理
v-model指令,绑定事件和 Watcher
现代 Vue 3 使用 Proxy 替代 Object.defineProperty,解决了无法检测新增属性的限制,但核心原理仍保持一致。





