Reflect 在 Vue3 中负责执行默认行为、绑定正确的 this 指向、统一返回值,缺一不可。
核心作用有三个:
一、### 与 Proxy 配合,在 getter/setter 拦截器中,必须先通过 Reflect 完成对象的原生读写行为,再插入响应式逻辑(依赖收集/触发更新)。Proxy 拦截,Reflect 执行。
在 Vue3 的 packages/reactivity/src/baseHandlers.ts 中,创建 get 拦截器的核心函数 createGetter 内部,第一句关键代码就是:
ts
const res = Reflect.get(target, key, receiver)
之后才会执行依赖收集 track(target, TrackOpTypes.GET, key) 并返回结果。
同样,在 set 拦截器中:
ts
const result = Reflect.set(target, key, value, receiver)
if (result) {
trigger(target, TriggerOpTypes.SET, key, value)
}
Proxy 的 get/set 陷阱(trap)本身并不会真正读取或修改对象,它们只是"拦截"了操作。真正去读取或修改底层数据必须由 Reflect 完成。这就好比门卫(Proxy)拦住了访客(操作),但最终开门、放行、登记(读写数据)这些实际动作还是得由内部工作人员(Reflect)来做。没有 Reflect,门卫只能干瞪眼,什么都做不了。
二、解决 this 指向问题
这是 Reflect 不可替代的根本原因
直接 target[key] 访问属性时,如果属性是一个方法,方法内部的 this 指向原始对象,绕过了 Proxy 拦截,依赖收集会断裂。
而 Reflect.get(target, key, receiver) 的第三个参数 receiver,能确保 this 始终指向代理对象,保证所有属性访问都经过 Proxy 拦截,响应式链条完整。没有这个参数,Vue3 在对象方法调用、继承、Map/Set 等集合类型场景下都会出现响应式断裂。
在 createGetter 中,Reflect.get 的调用始终传入了 receiver:
ts
const res = Reflect.get(target, key, receiver)
这里的 receiver 就是代理对象本身(通常是 proxy)。在 JavaScript 规范中,当访问的属性是一个 getter 或方法时,receiver 会作为 this 参数传入该 getter 或方法。
如果你写成 target[key],那么 getter/方法内部的 this 就是原始对象 target,后续对该 getter 内部其他属性的访问就不会再经过代理,依赖收集就会丢失。
js
const obj = {
name: 'Vue',
get alias() {
return this.name + '3' // 这里的 this 极其关键
}
}
const proxy = new Proxy(obj, handler)
当你访问 proxy.alias 时:
- 若拦截器里用
target[key]读取,此时 getter 内部的this是obj(原始对象),this.name就直接从obj上读取,不会触发代理的get,依赖收集不到name属性。 - 若用
Reflect.get(target, key, receiver),this就是proxy,this.name会再次触发代理的get拦截,从而正确收集到name的依赖。
对于 Map、Set 这类集合,Vue3 专门在 collectionHandlers.ts 中重写了方法(如 get、set、has),并且通过 instrumentations 对象将方法内的 this 绑定为代理对象,确保内部操作也走代理。而 Reflect 的 receiver 机制正是实现这种绑定的基础。
三、统一、规范的返回值
Reflect.set 返回布尔值表示操作是否成功,Reflect.deleteProperty 同理。这让 Vue3 能准确判断操作结果,只在成功时触发更新,避免异常中断。
在 baseHandlers.ts 的 set 拦截器中:
ts
const result = Reflect.set(target, key, value, receiver)
if (result) {
trigger(target, TriggerOpTypes.SET, key, value)
}
return result
在 deleteProperty 拦截器中:
ts
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
trigger(target, TriggerOpTypes.DELETE, key, undefined)
}
return result
普通赋值 target[key] = value 在严格模式下如果失败会抛出异常,而且返回值总是 value,无法知道是否真正写入成功(比如对象被冻结时赋值会静默失败)。Reflect.set 直接返回 true/false,Vue3 可以根据这个布尔值决定是否需要触发更新。如果操作失败,就不更新,保证了响应式系统的健壮性。
Reflect.get 和直接 targetkey 在 Vue3 源码中有什么具体差异?
在 Vue3 源码的 baseHandlers.ts 中,get 拦截器统一使用 Reflect.get(target, key, receiver)。如果不传 receiver,当访问继承自原型链的属性时,this 会指向原型对象而非代理对象,导致依赖收集错误。而 receiver 参数确保了无论属性来自对象自身还是原型链,this 始终指向代理对象。这也是 Vue3 能正确代理 Map、Set 等集合类型的原因------这些集合的内部方法依赖正确的 this 指向。
查看 Vue3 的 createGetter 完整逻辑
ts
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 处理各种特殊 key(如 symbol、isReactive 等)
// ...
const res = Reflect.get(target, key, receiver)
// 依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果得到的是对象且需要深层响应式,则递归转为 reactive
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
receiver 被原样传入 Reflect.get。如果直接使用 target[key],那么就没有 receiver 参数可言,JavaScript 引擎会默认将 this 绑定为 target(即原始对象),这会导致前面提到的依赖断裂。
对于集合类型(Map/Set),Vue3 在 collectionHandlers.ts 中通过 get 拦截器返回包装后的方法,这些方法在执行时会调用 this(代理对象)上的 get/set 等,从而继续走代理拦截。这依赖于 Reflect.get 传入的 receiver,因为 receiver 就是代理对象,当读取 map.get 方法时,方法内的 this 会被绑定为 receiver,即代理对象。如果不用 Reflect,this 将是原始 Map 对象,其内部操作无法被代理拦截。
Reflect 在 Vue3 响应式系统中绝不仅仅是语法糖,它解决了三个核心问题:
- 提供执行默认行为的唯一可靠方式;
- 通过
receiver参数确保this指向代理对象,保障依赖收集完整; - 用布尔返回值准确判断操作成功与否,驱动精准更新。
这就是为什么 Vue3 源码中几乎所有的 Proxy 陷阱都配套使用了对应的 Reflect 方法。