Vue 的响应式原理是如何实现的?
Vue 响应式通过数据劫持和观察者模式实现视图自动更新,主要采用 Object.defineProperty 或 Proxy。
Vue 的响应式原理主要通过数据劫持和观察者模式实现,能够在数据变化时自动触发视图更新。响应式的核心在于将普通对象转换为响应式对象,并结合依赖收集来实现。响应式的实现机制主要有 Vue2 和 Vue3 两个版本的差异。
Vue2 的响应式原理
Vue2 主要基于 Object.defineProperty
进行数据劫持,并采用发布-订阅模式。以下是关键步骤:
- 数据劫持 (使用
Object.defineProperty
):在初始化组件时,Vue 对data
选项中的对象进行递归遍历,并使用Object.defineProperty
将每个属性转换为 getter 和 setter。- Getter 在读取属性值触发依赖收集。
- Setter 在修改属性值触发更新通知。
示例代码展示属性劫持的基本逻辑: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) { val = newVal; dep.notify(); // 通知依赖更新 } } }); }
在这个例子中,任何对属性的操作会被拦截。
- 依赖收集 (使用 Dep 和 Watcher):
- Dep(依赖容器):每个属性对应一个 Dep,存储所有依赖它的 Watcher 实例。
- Watcher:代表一个视图渲染函数或计算属性。组件首次渲染时,会创建渲染 Watcher,触发属性的 getter,将 Watcher 记录到 Dep 中。此后修改属性值触发 setter,调用
dep.notify()
通知所有 Watcher 重新执行回调(如视图更新)。
- 数组处理:由于
Object.defineProperty
无法直接劫持数组变化操作(如push
),Vue2 重写了 7 个数组方法(如 push, pop),使它们在调用时会触发更新通知。- 局限性:无法检测属性新增或删除,且递归初始化可能导致性能问题。
Vue3 的响应式原理
Vue3 改用 Proxy
和 Reflect
实现更高效的现代响应式系统,提升了灵活性。
- 数据劫持 (使用
Proxy
):直接创建目标对象的代理对象。Proxy
可拦截属性的读取 (get)、修改 (set)、删除 (deleteProperty) 等所有操作。Reflect
用于安全反射到源对象。这使 Vue3 能无缝支持对象新增属性、数组索引变化等场景。
示例代码演示Proxy
的应用:const reactive = (target) => { const handler = { 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; } }; return new Proxy(target, handler); }; const obj = reactive({ a: 1 }); // 将对象转化为响应式 obj.a = 2; // 触发视图更新
这里
track
用于收集依赖,trigger
用于通知更新。
-
依赖收集:使用更灵活的
reactive
工具。读取时(在 get 拦截器)收集依赖(惰性方式,而非 Vue2 的递归初始化)。修改操作在 set 后自动通知依赖。 - 优势优化:
- 支持动态属性变化(如添加或删除键),无需特殊方法。
- 使用
ref
和reactive
的组合 APIs 提供更好的开发体验。 - 避免 Vue2 的性能问题:依赖收集懒惰执行,减少初始化代价。
响应式系统工作流程
简化流程:
- 初始化时,将
data
对象转换为响应式(Observer)。 - 组件渲染触发属性的 getter,收集当前 Watcher 作为依赖。
- 数据被修改触发 setter(或 Proxy set),Dep/Proxy 通过回调通知关联 Watcher。
- Watcher 执行重新渲染等操作,视图自动更新。
该机制确保了框架的响应式更新无需手动 DOM 操作。