Vue 的双向数据绑定是通过 数据劫持 + 发布-订阅模式 实现的,具体分为以下三个关键机制:
1. 数据劫持(响应式系统)
Vue 使用 Object.defineProperty(Vue 2)或 Proxy(Vue 3)监听数据变化。
Vue 2 实现:
// 简化版数据劫持
function defineReactive(obj, key) {let value = obj[key];const dep = new Dep(); // 依赖收集器Object.defineProperty(obj, key, {get() {if (Dep.target) {dep.addSub(Dep.target); // 收集依赖(Watcher)}return value;},set(newVal) {if (newVal === value) return;value = newVal;dep.notify(); // 通知所有订阅者更新}});
}
Vue 3 改进:
// 使用Proxy实现
const reactive = (target) => {return new Proxy(target, {get(target, key, receiver) {track(target, key); // 依赖收集return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver);trigger(target, key); // 触发更新return true;}});
};
2. 依赖收集(发布-订阅模式)
Dep 类:管理依赖(一个属性对应一个Dep)Watcher 类:订阅数据变化,触发更新
class Dep {constructor() {this.subs = [];}addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}
}class Watcher {update() {// 执行更新(如重新渲染组件)}
}
3. 模板编译(指令解析)
Vue 编译器将模板中的指令(如 v-model)转换为绑定代码:
<input v-model="message">
编译后相当于:
<input :value="message" @input="message = $event.target.value"
>
双向绑定工作流程
初始化阶段:遍历 data 对象,用 Object.defineProperty/Proxy 劫持所有属性编译模板,为每个绑定创建 Watcher数据访问时(触发 getter):当前 Watcher 被添加到属性的 Dep 中数据修改时(触发 setter):Dep 通知所有 Watcher 更新Watcher 触发组件重新渲染
4、Vue 2 vs Vue 3 实现对比
特性 | Vue 2 | Vue 3 |
---|---|---|
核心API | Object.defineProperty | Proxy |
监听范围 | 仅能劫持已定义属性 | 动态新增属性自动响应 |
数组处理 | 需重写数组方法 | 直接监听数组变化 |
性能 | 递归遍历所有属性 | 惰性监听,按需触发 |
5、常见问题解答
Q1:为什么 Vue 3 改用 Proxy?
解决 Vue 2 无法检测对象/数组新增属性的问题性能更好(无需递归初始化所有属性)
Q2:v-model 在自定义组件中的原理?
<CustomComponent v-model="value" />
等价于:
<CustomComponent :modelValue="value"@update:modelValue="newVal => value = newVal"
/>
手写极简双向绑定
// 简易版Vue响应式
class MiniVue {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) {value = newVal;dep.notify();}});});}compile(el) {const element = document.querySelector(el);this.walk(element);}walk(node) {if (node.nodeType === 1) { // 元素节点Array.from(node.attributes).forEach(attr => {if (attr.name.startsWith('v-')) {const dir = attr.name.substring(2);if (dir === 'model') {const key = attr.value;node.value = this.$data[key];new Watcher(this.$data, key, val => {node.value = val;});node.addEventListener('input', e => {this.$data[key] = e.target.value;});}}});}// 递归子节点...}
}
通过这种机制,Vue 实现了数据变化自动更新视图、视图输入自动更新数据的双向绑定效果。