vue实现双向绑定模拟
Vue 双向绑定模拟实现
Vue 的双向绑定核心是通过数据劫持结合发布-订阅模式实现。以下是一个简化版的模拟实现:
数据劫持(Object.defineProperty)
通过 Object.defineProperty 劫持对象的属性,在属性被访问或修改时触发 getter 和 setter:
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(); // 每个属性对应一个 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;
observe(newVal); // 新值是对象时继续劫持
dep.notify(); // 通知更新
}
});
}
依赖收集(Dep 类)
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(); // 触发依赖收集
}
get() {
Dep.target = this;
const value = this.vm._data[this.exp]; // 触发 getter
Dep.target = null;
return value;
}
update() {
const newValue = this.vm._data[this.exp];
if (newValue !== this.value) {
this.value = newValue;
this.cb.call(this.vm, newValue);
}
}
}
编译器(Compile)
简化版模板编译,处理 v-model 指令:
function compile(node, vm) {
if (node.nodeType === 1) { // 元素节点
const attrs = node.attributes;
Array.from(attrs).forEach(attr => {
if (attr.name === 'v-model') {
const exp = attr.value;
node.value = vm._data[exp];
new Watcher(vm, exp, value => {
node.value = value;
});
node.addEventListener('input', e => {
vm._data[exp] = e.target.value;
});
}
});
}
}
完整示例
<div id="app">
<input v-model="message">
<p>{{ message }}</p>
</div>
<script>
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data);
compile(document.querySelector(options.el), this);
}
}
new Vue({
el: '#app',
data: { message: 'Hello Vue' }
});
</script>
Proxy 实现方案(Vue 3)
Vue 3 改用 Proxy 实现响应式,解决了 Object.defineProperty 的局限性:
function reactive(data) {
if (typeof data !== 'object') return data;
const observed = new Proxy(data, {
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;
}
});
Object.keys(data).forEach(key => {
observed[key] = reactive(data[key]); // 递归处理
});
return observed;
}
关键点说明
- 数据劫持:通过拦截对象属性的访问和修改实现响应式
- 依赖收集:在 getter 中收集依赖,setter 中通知更新
- 发布-订阅模式:Dep 作为调度中心,Watcher 作为订阅者
- 批量异步更新:实际实现中会使用 nextTick 进行异步更新优化







