MVVM中的双向绑定是怎么回事
你有没有用过Vue或者Angular写表单?输入框一打字,数据立刻更新;反过来,改了数据,输入框内容也跟着变。这种“你变我也变”的默契,就是MVVM里的双向绑定在背后干活。
MVVM把界面(View)和数据(Model)通过一个叫ViewModel的东西连起来。View变了,Model自动更新;Model变了,View也实时刷新。不用手动操作DOM,也不用手动同步变量,省事又不容易出错。
数据劫持:让JS对象变得“可监听”
JavaScript本身没法直接知道某个属性什么时候被修改了。于是框架用了Object.defineProperty(Vue 2)或者Proxy(Vue 3)来“监听”数据变化。
比如Vue 2里,当你定义data(){ return { name: '张三' } },框架会遍历这些属性,用defineProperty把它们变成getter和setter:
let val = '张三';
Object.defineProperty(data, 'name', {
get() {
console.log('有人读取name');
return val;
},
set(newVal) {
console.log('name被改了');
val = newVal;
updateView(); // 通知视图更新
}
});每次读name,get触发;每次赋值,set就知道该干活了。
模板解析与依赖收集
光监听数据还不够,得知道哪个视图部分依赖哪个数据。比如模板里写了<input v-model="name">,那这个input就和name绑定了。
初始化时,Vue会解析模板,发现v-model指向name,于是建立一条“name变化 → input更新”的联系。这个过程叫依赖收集。就像你订了天气预报短信,只要天气一变,马上推给你。
内部实现上,每个响应式数据都有个“小本本”(Dep),记录着哪些视图节点在等它更新。一旦set被触发,就遍历这个小本本,通知所有人刷新。
视图更新的自动同步
用户在输入框打字,触发input事件,ViewModel捕获到后,去更新对应的Model字段。由于这个字段是响应式的,set一执行,所有依赖它的视图区域就会重新渲染。
反过来,如果在代码里写this.name = '李四',同样触发set,input框的内容也会变成“李四”。两边互相同步,谁都不掉队。
实际场景中的表现
想象你在写一个用户资料页。姓名、年龄、邮箱都在表单里。右边实时预览卡片,显示当前输入的信息。
传统做法是你得给每个input加事件,拿到值再手动塞进预览区的DOM里。现在只需要把所有字段交给ViewModel,模板里直接{{ name }}、{{ age }},一切自动同步。代码干净,维护也轻松。
Vue 3的升级:用Proxy代替defineProperty
Vue 2的defineProperty有个短板:无法监听对象新增属性或数组下标赋值。Vue 3换上了Proxy,直接代理整个对象,不管怎么改都能捕获。
const data = { name: '张三' };
const proxy = new Proxy(data, {
get(target, key) {
console.log(`读取${key}`);
return target[key];
},
set(target, key, value) {
console.log(`设置${key}为${value}`);
target[key] = value;
updateView();
return true;
}
});Proxy能力更强,代码更简洁,是双向绑定的进化版。
双向绑定不是魔法,而是基于JS语言特性+观察者模式的一套精巧设计。搞明白它,不只是为了面试答题,更是为了在调试问题、优化性能时,知道底层到底发生了什么。