双向绑定原理
Vue 的双向绑定是通过 v-model 指令实现的,它实际上是语法糖,结合了属性绑定和事件监听:
- 数据到视图的绑定:通过数据劫持(Object.defineProperty 或 Proxy)和发布-订阅模式实现
- 视图到数据的绑定:通过监听表单元素的 input 或 change 事件实现
手动实现双向绑定
1. 使用 Object.defineProperty 实现
// 简易双向绑定实现
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(); // 通知更新
}
});
}
// 依赖收集器
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
// 观察者
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.vm[this.key]; // 触发getter,收集依赖
Dep.target = null;
}
update() {
this.cb.call(this.vm, this.vm[this.key]);
}
}
// 实现v-model
function compile(el, vm) {
el.querySelectorAll('[v-model]').forEach(node => {
const key = node.getAttribute('v-model');
new Watcher(vm, key, val => {
node.value = val;
});
node.addEventListener('input', e => {
vm[key] = e.target.value;
});
});
}
// Vue实例
class MyVue {
constructor(options) {
this.$data = options.data;
this._observe(this.$data);
this._compile(document.querySelector(options.el));
}
_observe(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
});
}
_compile(el) {
compile(el, this);
}
}
2. 使用 Proxy 实现(Vue3 方式)
class MyVue {
constructor(options) {
this.$data = options.data;
this.$el = document.querySelector(options.el);
this._proxy(this.$data);
this._compile(this.$el);
}
_proxy(data) {
const handler = {
get(target, key) {
return target[key];
},
set(target, key, value) {
target[key] = value;
// 这里应该通知更新视图
return true;
}
};
this.$data = new Proxy(data, handler);
}
_compile(el) {
el.querySelectorAll('[v-model]').forEach(node => {
const key = node.getAttribute('v-model');
node.value = this.$data[key];
node.addEventListener('input', e => {
this.$data[key] = e.target.value;
});
});
}
}
实际应用示例
<div id="app">
<input v-model="message">
<p>{{ message }}</p>
</div>
<script>
// 使用上面实现的MyVue
const app = new MyVue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
总结
- Vue2 使用 Object.defineProperty 实现数据劫持
- Vue3 使用 Proxy 实现数据劫持,性能更好且能监听数组变化
- 双向绑定的核心是数据劫持 + 发布订阅模式
- v-model 本质上是语法糖,结合了 :value 和 @input
在实际面试中,可以结合自己的理解,从响应式原理、依赖收集、派发更新等方面展开说明。