文章目录
- 前言
- [一、Proxy vs defineProperty 响应式实现机制](#一、Proxy vs defineProperty 响应式实现机制)
-
- [1.1 defineProperty 实现机制](#1.1 defineProperty 实现机制)
- [1.2 Proxy 实现机制](#1.2 Proxy 实现机制)
- [二、Proxy 如何解决 defineProperty 的三大核心痛点](#二、Proxy 如何解决 defineProperty 的三大核心痛点)
-
- [2.1 动态属性问题](#2.1 动态属性问题)
- [2.2 数组操作支持](#2.2 数组操作支持)
- [2.3 性能瓶颈](#2.3 性能瓶颈)
- [三、Proxy + Reflect](#三、Proxy + Reflect)
-
- [3.1 Reflect API 的核心作用](#3.1 Reflect API 的核心作用)
-
- [3.1.1 保持正确上下文( receiver 传递)](#3.1.1 保持正确上下文( receiver 传递))
- [3.1.2 标准化操作结果(Reflect API)](#3.1.2 标准化操作结果(Reflect API))
- [3.1.3 元操作能力支持(Symbol/内部属性)](#3.1.3 元操作能力支持(Symbol/内部属性))
- [3.2 Proxy 与 Reflect 关系解析](#3.2 Proxy 与 Reflect 关系解析)
- 总结
-
- [1. 机制革新:](#1. 机制革新:)
- [2. 三大痛点突破:](#2. 三大痛点突破:)
前言
Vue 的响应式系统是其核心特性之一,它使数据变化能够自动反映到视图上。Vue 2 采用 Object.defineProperty
实现响应式,而 Vue 3 则全面转向 Proxy
。这一转变解决了 defineProperty 的三个主要痛点:
- 动态属性问题:无法检测新增/删除的属性
- 数组支持有限:无法检测索引设置和长度变化
- 性能瓶颈:初始化时需要递归遍历所有属性
一、Proxy vs defineProperty 响应式实现机制
1.1 defineProperty 实现机制
javascript
function defineReactive(obj, key) {
// 一个 Dep(Dependency)实例,用于管理依赖(Watcher)
const dep = new Dep()
let val = obj[key]
Object.defineProperty(obj, key, {
get() {
dep.depend() // 依赖收集(当前 Watcher 订阅此属性)
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
dep.notify() // 通知所有 Watcher 更新
}
})
}
Vue 2.x 的响应式系统基于 观察者模式,核心是:
- Dep(Dependency):管理某个属性的所有依赖(Watcher)。
- Watcher:代表一个依赖(如组件的 render 函数、computed 计算属性等)。
- defineReactive 的
核心流程
初始化
:用 Object.defineProperty 劫持 obj[key]。
读取时
(get):调用 dep.depend(),让当前 Watcher 订阅此属性。
修改时
(set):如果值变化,调用 dep.notify() 通知所有 Watcher 更新。
初始化时递归转换所有嵌套属性
1.2 Proxy 实现机制
javascript
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, key) // 值变化时才触发更新
}
return result
}
})
}
关键组成部分
-
Proxy 拦截器
get 陷阱:在属性被访问时触发
set 陷阱:在属性被修改时触发
-
Reflect 的使用
通过 Reflect.get/set 保持默认行为
确保 this 绑定正确(通过 receiver 参数)
-
响应式系统核心
track(target, key):收集当前正在运行的 effect
trigger(target, key):通知相关 effect 重新执行
惰性劫持整个对象
二、Proxy 如何解决 defineProperty 的三大核心痛点
2.1 动态属性问题
defineProperty 的困境:
javascript
const obj = { a: 1 }
Vue.observable(obj)
// 新增属性无法被检测
obj.b = 2 // ✗ 不会触发更新
Proxy 的解决方案:
javascript
const proxy = reactive({ a: 1 })
// 动态添加属性
proxy.b = 2 // ✓ 触发 set 拦截
实现原理:
- Proxy 拦截的是整个对象的操作入口(不需要预先定义属性描述符)
- 任何属性的增删改查都会触发对应的 trap
2.2 数组操作支持
defineProperty 的妥协方案:
javascript
// Vue 2 必须重写数组方法
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
;['push', 'pop'].forEach(method => {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
dep.notify() // 手动触发更新
return result
})
})
Proxy 的天然支持:
javascript
const arr = reactive([1, 2, 3])
// 所有数组操作都能被拦截
arr.push(4) // ✓ 触发 set
arr.length = 1 // ✓ 触发 set
arr[0] = 5 // ✓ 触发 set
2.3 性能瓶颈
defineProperty 的性能陷阱:
javascript
// 初始化时递归转换所有嵌套属性
function defineReactive(obj) {
Object.keys(obj).forEach(key => {
let val = obj[key]
if (typeof val === 'object') {
defineReactive(val) // 立即递归
}
// 定义属性描述符...
})
}
Proxy 的惰性处理:
javascript
const obj = reactive({
a: { b: { c: 1 } }
})
// 只有访问到的层级才会被代理
console.log(obj.a.b.c)
// 访问链:obj → obj.a (创建代理) → obj.a.b (创建代理) → obj.a.b.c
Proxy 的惰性劫持机制使得 Vue 3 在大型对象处理上获得显著性能提升
特性 | Vue 2 (defineProperty) | Vue 3 (Proxy) |
---|---|---|
初始化方式 | 递归遍历所有属性 | 按需代理(惰性劫持) |
数组支持 | 需要特殊处理 | 原生支持 |
动态属性 | 需要 Vue.set | 自动支持 |
性能 | 初始化性能较差 | 运行时性能更优 |
拦截操作 | 仅 get/set | 13 种拦截操作 |
三、Proxy + Reflect
Reflect 是一个内置的对象
,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler
的方法一一对应。Reflect 不是一个函数对象,因此它是不可构造的。Reflect 的所有属性和方法都是静态的。
对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了
Reflect
对象
因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。
3.1 Reflect API 的核心作用
Reflect 不是 Vue 特有的 API,但它与 Proxy 配合解决了几个关键问题:
javascript
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 使用 Reflect 保证正确的 this 指向
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
// 返回操作是否成功的布尔值
return Reflect.set(target, key, value, receiver)
}
})
3.1.1 保持正确上下文( receiver 传递)
Vue 2 缺陷
javascript
const parent = { foo: 1 }
const child = {}
Object.setPrototypeOf(child, parent)
// Vue 2 实现
defineReactive(child, 'foo') // 无法正确触发 parent 的 getter
问题:Object.defineProperty 直接操作 target[key],会切断原型链访问。
Vue 3 解决方案
javascript
const parent = reactive({ foo: 1 })
const child = reactive(Object.create(parent))
// Proxy 的 get 陷阱
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver) // 关键:传递 receiver
}
- receiver 参数始终指向当前代理对象,保持原型链访问
- 访问 child.foo 时:
先查找 child 自身属性
未找到时通过原型链访问 parent.foo
仍能正确触发 parent 的响应式逻辑
3.1.2 标准化操作结果(Reflect API)
Vue 2 的问题场景
javascript
const obj = {}
Object.defineProperty(obj, 'foo', {
writable: false,
value: 1
})
// Vue 2 的 set 实现
try {
obj.foo = 2 // 直接赋值会抛出 TypeError
} catch (e) {
console.error(e) // 需要 try-catch 处理
}
Vue 3 的改进实现
javascript
set(target, key, value, receiver) {
const oldValue = target[key]
const success = Reflect.set(target, key, value, receiver) // 返回布尔值
if (!success) {
console.warn(`属性 ${String(key)} 不可写`)
return false
}
if (hasChanged(value, oldValue)) {
trigger(target, key)
}
return success
}
3.1.3 元操作能力支持(Symbol/内部属性)
Vue 2 的局限性
javascript
const obj = { [Symbol.iterator]: function() {} }
// Vue 2 无法监听的场景:
defineReactive(obj, Symbol.iterator) // 报错:Symbol 不能作为键
obj[Symbol.toStringTag] = 'Custom' // 无法响应
Vue 3 的完整支持
javascript
const sym = Symbol('description')
const obj = reactive({})
// 支持 Symbol 属性
obj[sym] = 'value' // 正常触发响应式
// 支持所有 Reflect 方法
Reflect.get(obj, sym)
Reflect.ownKeys(obj) // 包含 Symbol 键
维度 | Vue 2 (defineProperty) | Vue 3 (Proxy + Reflect) |
---|---|---|
原型链支持 | ❌ 需要手动处理继承 | ✅ 自动通过 receiver 传递 |
错误处理 | ⚠️ 依赖 try-catch | ✅ 标准化布尔返回值 |
元编程支持 | ❌ 仅支持字符串键 | ✅ 完整 Symbol/Reflect 操作 |
性能影响 | ⚠️ 初始化递归遍历 | ✅ 按需代理 |
代码可维护性 | ❌ 分散的特殊处理逻辑 | ✅ 统一拦截层 |
3.2 Proxy 与 Reflect 关系解析
-
Proxy 的角色
拦截层:创建对象的虚拟代理,拦截13种基本操作
自定义行为:允许开发者修改对象的默认行为
透明访问:对外保持与原对象相同的接口
-
Reflect 的角色
反射层:提供操作对象的标准化方法
默认行为:包含与Proxy拦截器一一对应的静态方法
元操作:支持符号属性等高级操作
Proxy定义拦截,Reflect提供默认实现
Proxy 的完整拦截能力
拦截操作 | 对应 Reflect 方法 |
---|---|
get | Reflect.get |
set | Reflect.set |
has | Reflect.has |
deleteProperty | Reflect.deleteProperty |
... | ... |
Proxy + Reflect 黄金组合:
保持默认行为的同时支持自定义拦截
正确处理原型链和 this 绑定问题
提供类型安全的操作结果(返回布尔值)
总结
1. 机制革新:
- 从 Object.defineProperty 的静态劫持升级为 Proxy 的动态代理
- 拦截操作从仅限 get/set 扩展到 13 种对象基本操作
- 通过 Reflect 实现标准化元编程,解决了上下文传递等关键问题
2. 三大痛点突破:
- 动态属性:无需特殊 API 自动检测属性增删
- 数组支持:原生支持所有变异方法及索引操作
- 性能优化:惰性代理机制降低初始化开销