1. 核心实现方式
Vue 2:基于 Object.defineProperty
通过遍历数据对象的属性,使用Object.defineProperty为每个属性添加getter和setter,从而拦截属性的读取和修改操作。
原理:当属性被访问时(getter),收集依赖(记录当前组件的 Watcher);当属性被修改时(setter),触发依赖更新(通知 Watcher 重新渲染)。Vue 3:基于 Proxy
使用 ES6 的Proxy代理整个数据对象,直接拦截对象的 读取、修改、新增、删除 等操作(包括属性的动态添加/删除)。
原理:通过Proxy创建数据的代理对象,当对代理对象进行操作时,触发对应的拦截方法(如get、set、deleteProperty等),进而收集依赖或触发更新。
2. 对数据类型的支持
Vue 2:
- 仅能拦截对象的 已有属性,无法直接监测 新增属性 或 删除属性(需通过
Vue.set/this.$set或Vue.delete/this.$delete手动触发响应式)。 - 对数组的拦截有限:通过重写数组的 7 个变更方法(
push、pop、shift、unshift、splice、sort、reverse)实现响应式,而直接通过索引修改数组元素(如arr[0] = 1)或修改数组长度(如arr.length = 0)无法触发更新(需手动处理)。
- 仅能拦截对象的 已有属性,无法直接监测 新增属性 或 删除属性(需通过
Vue 3:
Proxy能原生拦截对象的 所有操作,包括新增属性(obj.newKey = value)、删除属性(delete obj.key),无需手动调用 API。- 对数组的支持更完善:直接通过索引修改元素(
arr[0] = 1)或修改长度(arr.length = 0)都能被Proxy拦截,自动触发响应式更新,无需重写数组方法。
3. 初始化性能
Vue 2:
在初始化时需要 递归遍历对象的所有属性,为每个属性添加getter/setter,对于嵌套较深或属性较多的对象,初始化性能开销较大。Vue 3:
Proxy是 懒代理 模式,初始化时仅代理顶层对象,嵌套对象的代理会在首次访问时动态创建(即“惰性递归”)。因此,初始化大型对象时性能更优,尤其适合数据结构复杂的场景。
4. 依赖收集粒度
Vue 2:
依赖收集以 属性为单位,每个属性对应一个依赖列表。当属性更新时,仅通知依赖该属性的 Watcher。Vue 3:
依赖收集以 对象为单位,但通过Proxy的拦截能力,可以更精确地定位变化(如具体修改的属性)。同时,Vue 3 引入了 WeakMap + Map 的依赖存储结构,进一步优化了依赖管理的效率
5. 对非对象类型的处理
Vue 2:
对基本类型(如字符串、数字)的响应式依赖于对象属性的包装(即需将基本类型放在对象中,如{ count: 0 }),否则无法拦截。Vue 3:
虽然Proxy本质上代理对象,但通过ref语法糖,将基本类型包装为一个带有value属性的对象,间接实现了基本类型的响应式(ref内部仍基于Proxy或Object.defineProperty实现,取决于版本)。
总结
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 核心 API | Object.defineProperty | Proxy |
| 新增/删除属性 | 需手动调用 Vue.set/delete | 原生支持,自动响应 |
| 数组索引/长度修改 | 不支持(需手动处理) | 原生支持 |
| 初始化性能 | 递归遍历所有属性,开销较大 | 懒代理,初始化性能更优 |
| 依赖收集粒度 | 以属性为单位 | 以对象为单位,定位更精确 |
Vue 3 的响应式原理在灵活性、性能和对数据操作的全面性上都优于 Vue 2,这也是 Vue 3 性能提升的重要原因之一。