1. 数据劫持
① Vue 2 使用defineproperty()
在 Vue 2.x 中,Vue 使用 Object.defineProperty()
来拦截对象属性的getter和setter。每当一个 Vue 实例被创建时,它会遍历所有传入的数据对象的属性,并将它们转换为 getter 和 setter。
javascript
// 简化版示例
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
// 收集依赖
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) return;
val = newVal;
// 触发更新
}
});
}
局限性:
- 它不能检测到新增或删除属性。
- 对于数组的操作无法直接拦截,因此 Vue 需要重写数组的部分方法来实现响应式。
② Vue 3 使用 Proxy
从 Vue 3 开始,Vue 引入了 ES6 的 Proxy
来替代 Object.defineProperty()
。Proxy
提供了更强大的功能,可以拦截更多的操作类型,如添加新属性、删除属性等,并且可以递归地处理嵌套对象,而不需要像 Object.defineProperty()
那样手动遍历每个属性。
javascript
// 简化版示例
const handler = {
get(target, key, receiver) {
// 收集依赖
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 触发更新
return result;
}
};
const proxy = new Proxy(data, handler);
2. 依赖收集
Vue 使用观察者模式来实现依赖收集。每当渲染组件时,Vue 会执行模板中表达式的 getter,从而触发依赖收集过程。在此过程中,Vue 会记录哪些 watcher 订阅了特定的数据属性的变化。
-
Watcher:每一个需要响应数据变化的地方(例如,计算属性、渲染函数)都有一个对应的 watcher。watcher 负责监听它所关心的数据属性的变化,并在变化发生时更新相关的视图部分。
-
Dep(依赖):每个响应式属性都有一个关联的 Dep 实例,用来管理所有订阅它的 watcher。当该属性发生变化时,Dep 会通知所有的 watcher 进行更新。
3. 视图更新机制
当用户交互或其他代码导致数据属性发生变化时,Vue 的响应式系统会调用该属性的 setter 方法。setter 不仅会设置新的值,还会通知所有订阅了该属性变化的 watcher。然后,这些 watcher 会重新评估并触发视图更新。
-
异步队列 :为了提高性能,Vue 将所有的 DOM 更新操作批量放入一个异步队列中,在下一个事件循环 tick 时统一执行。这减少了不必要的重绘和回流,提升了应用的效率。
javascriptqueueWatcher(watcher) { // 如果 watcher 已经在队列中,则跳过 if (!watcher.id) { watcher.id = nextId++; queue.push(watcher); } // 批量更新 nextTick(flushSchedulerQueue); }
4. 计算属性与侦听器
除了直接绑定到数据属性外,Vue 还提供了计算属性(computed properties)和侦听器(watchers),允许开发者基于现有数据派生出新的状态或者执行副作用操作。
-
计算属性:计算属性是缓存的,只有在其依赖的数据发生变化时才会重新计算。它们非常适合用于复杂逻辑的推导或格式化输出。
-
侦听器:侦听器则可以用来执行更复杂的逻辑,比如异步操作或开销较大的任务。侦听器可以在数据变化时立即执行,也可以指定深度监听。
5. Vue 3 中的改进
Vue 3 在多个方面对响应式系统进行了优化:
-
更好的性能 :通过使用
Proxy
和更加高效的依赖跟踪机制,Vue 3 显著提高了响应式的性能。 -
更简单的 API:引入了 Composition API,使得逻辑复用更加简单直观,同时支持 TypeScript 更加友好。
-
细粒度的响应式 :Vue 3 提供了
reactive
和ref
两种方式来创建响应式对象,其中ref
适用于单一值的响应式封装,而reactive
则用于对象或数组。 -
内置工具函数 :如
toRefs
和isRef
,帮助开发者更好地管理和操作响应式对象。