[Hard] 📄 双向数据绑定
1. Please explain the underlying principle of how Vue2 and Vue3 each implement two-way binding
请解释 Vue2 和 Vue3 各自如何实现双向绑定的底层原理?
要理解 Vue 的双向绑定,需要先明白响应式系统的运作机制,以及 Vue2 与 Vue3 在实现上的差异。
Vue2 的实现方式
Vue2 使用 Object.defineProperty 来实现双向绑定,这个方法可以将一个对象的属性包装成 getter 和 setter,并且可以监听对象属性的变化。流程如下:
1. Data Hijacking(数据劫持)
在 Vue2 中,当一个组件中某个数 据的对象被创建时,Vue 会遍历整个对象中的所有属性,并使用 Object.defineProperty 将这些属性转换成 getter 和 setter,这才使 Vue 可以追踪数据的读取和修改。
2. Dependency Collection(依赖收集)
每当组件中的渲染函数被执行时,会读取 data 中的属性,这时候就会触发 getter,Vue 会记录这些依赖,确保当数据变化时,能够通知到依赖这些数据的组件。
3. Dispatching Updates(派发更新)
当数据被修改时,会触发 setter,这时候 Vue 会通知到所有依赖这些数据的组件,并且重新执行渲染函数,更新 DOM。
Vue2 代码示例
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log(`get ${key}: ${val}`);
return val;
},
set: function reactiveSetter(newVal) {
console.log(`set ${key}: ${newVal}`);
val = newVal;
},
});
}
const data = { name: 'Pitt' };
defineReactive(data, 'name', data.name);
console.log(data.name); // 触发 getter,输出 "get name: Pitt"
data.name = 'Vue2 Reactivity'; // 触发 setter,输出 "set name: Vue2 Reactivity"
Vue2 的限制
使用 Object.defineProperty 存在一些限制:
- 无法侦测对象属性的新增或删除:必须使用
Vue.set()或Vue.delete() - 无法侦测数组索引的变化:必须使用 Vue 提供的数组方法(如
push、pop等) - 性能问题:需要递归遍历所有属性,预先定义 getter 和 setter
Vue3 的实现方式
Vue3 引入了 ES6 的 Proxy,这个方法可以将一个对象包装成一个代理,并且可以监听对象属性的变化,同时性能更加优化。流程如下:
1. 使用 Proxy 进行数据劫持
在 Vue3 中会使用 new Proxy 创建对数据的代理,而不再是逐一对数据的属性进行定义 getter 和 setter,这样除了可以针对更细致的层面追踪数据变化,同时也能拦截更多类型的操作,例如属性的新增或删除。