在 ES6 中,Reflect 是一个内置的静态对象(不能实例化,类似 Math/Object),它将对象的一些底层操作方法(如属性访问、赋值、删除、函数调用等)规范化、模块化地封装起来,弥补了 Object 原有方法的设计缺陷,同时与 Proxy 形成完美配合,是 ES6 响应式(如 Vue3 响应式)的核心底层支撑之一。
一、核心设计目的
- 统一对象操作 API:将分散在
Object、in运算符、delete运算符等的底层操作,集中到Reflect上,语义更清晰; - 函数化替代命令式操作:把
in、delete等命令式操作转为函数调用,更易组合、复用; - 修正
Object方法的怪异行为:Object部分方法失败时抛异常,Reflect统一返回布尔值表示操作结果; - 能够在
Proxy中调用原始逻辑:Reflect的 13 个方法与Proxy的 13 个拦截器方法完全匹配,便于在Proxy中调用原始逻辑。
二、Reflect 核心方法(常用)
| Reflect 方法 | 对应传统操作 / Object 方法 | 作用 | 核心差异(对比传统方式) |
|---|---|---|---|
Reflect.get(target, key) | target[key] / Object.get | 获取对象属性值 | 支持第三个参数(receiver)绑定 this |
Reflect.set(target, key, val) | target[key] = val | 设置对象属性值 | 返回布尔值(是否成功),可绑定 this |
Reflect.has(target, key) | key in target | 判断属性是否存在 | 函数式调用,更易封装 |
Reflect.deleteProperty(target, key) | delete target[key] | 删除对象属性 | 返回布尔值(是否成功),不抛异常 |
Reflect.construct(target, args) | new target(...args) | 构造函数调用(无 new 关键字) | 更灵活,可模拟 new 行为 |
Reflect.apply(fn, thisArg, args) | fn.apply(thisArg, args) | 调用函数并指定 this 和参数 | 语义更清晰,与 Proxy.apply 匹配 |
Reflect.defineProperty(target, key, desc) | Object.defineProperty | 定义对象属性(如可枚举、可配置) | 返回布尔值(是否成功),不抛异常 |
三、核心特性 & 对比示例
1. 操作结果标准化:返回布尔值(替代异常)
Object.defineProperty 失败时抛异常,Reflect.defineProperty 返回 false,无需 try/catch:
// 传统 Object 方式(抛异常)
const obj = {};
try {
Object.defineProperty(obj, 'name', { value: '张三', writable: false });
Object.defineProperty(obj, 'name', { value: '李四' }); // 不可重写,抛异常
} catch (e) {
console.log('操作失败:', e);
}
// Reflect 方式(返回布尔值)
const success = Reflect.defineProperty(obj, 'name', { value: '李四' });
console.log('操作是否成功:', success); // false(无需 try/catch)
2. 函数化替代命令式操作
把 in、delete 等运算符转为函数,便于动态调用 / 封装:
const obj = { name: '张三' };
// 传统命令式
console.log('name' in obj); // true
delete obj.name;
console.log('name' in obj); // false
// Reflect 函数式
console.log(Reflect.has(obj, 'name')); // true
const deleteOk = Reflect.deleteProperty(obj, 'name');
console.log(deleteOk); // true
console.log(Reflect.has(obj, 'name')); // false
3. 绑定 this(receiver 参数)
Reflect.get/set 支持第三个参数 receiver,可修改属性访问时的 this 指向(Vue3 响应式核心用法):
const parent = {
name: '父对象',
get fullName() {
return this.name; // this 指向动态绑定
}
};
const child = { name: '子对象' };
// 传统方式:this 指向 parent
console.log(parent.fullName); // 父对象
// Reflect.get:指定 receiver 为 child,this 指向 child
console.log(Reflect.get(parent, 'fullName', child)); // 子对象
4. 与 Proxy 完美配合
Proxy 拦截对象操作时,可通过 Reflect 调用原始逻辑,保证行为一致性:
// 用 Proxy 拦截对象,Reflect 调用原始操作
const obj = new Proxy({ name: '张三' }, {
get(target, key, receiver) {
console.log('拦截获取属性:', key);
return Reflect.get(target, key, receiver); // 调用原始 get 逻辑
},
set(target, key, val, receiver) {
console.log('拦截设置属性:', key, '=', val);
return Reflect.set(target, key, val, receiver); // 调用原始 set 逻辑
}
});
obj.name = '李四'; // 打印:拦截设置属性:name = 李四
console.log(obj.name); // 打印:拦截获取属性:name → 输出 李四
控制台输出如下
这段代码是 ES6 Proxy(代理)和 Reflect(反射)配合使用的经典示例,核心目的是拦截对象的属性读取 / 赋值操作,同时通过 Reflect 保证原始的属性操作逻辑不丢失。下面逐行拆解代码的含义、执行流程和核心细节:
4.1、代码整体结构
// 用 Proxy 拦截对象,Reflect 调用原始操作
const obj = new Proxy(原始对象, 拦截器配置);
obj.name = '李四'; // 触发 set 拦截器
console.log(obj.name); // 触发 get 拦截器
new Proxy(target, handler):创建一个代理对象,target是被代理的原始对象(这里是{ name: '张三' }),handler是拦截器配置对象,定义了要拦截的操作(get/set等)。handler.get:拦截属性读取操作(如obj.name、obj['name'])。handler.set:拦截属性赋值操作(如obj.name = '李四')。Reflect.get/set:调用对象的原始操作逻辑(即不拦截时原本会执行的属性读取 / 赋值),保证操作的正确性。
4.2、逐行拆解核心代码
4.2.1. 创建 Proxy 代理对象
const obj = new Proxy({ name: '张三' }, {
// 拦截「属性读取」的钩子函数
get(target, key, receiver) {
console.log('拦截获取属性:', key);
return Reflect.get(target, key, receiver); // 执行原始的「读取属性」逻辑
},
// 拦截「属性赋值」的钩子函数
set(target, key, val, receiver) {
console.log('拦截设置属性:', key, '=', val);
return Reflect.set(target, key, val, receiver); // 执行原始的「赋值属性」逻辑
}
});
参数说明(get/set 钩子的参数完全对应 Reflect 的参数):
| 参数 | 含义 |
|---|---|
target | 被代理的原始对象(这里是 { name: '张三' }) |
key | 要读取 / 赋值的属性名(如 name) |
val | (仅 set 有)要赋值的新值(如 李四) |
receiver | 触发操作的上下文对象(这里就是 Proxy 代理对象 obj 本身),用于绑定 this 指向 |
4.2.2. 执行 obj.name = '李四'(触发 set 拦截器)
执行赋值操作时,不会直接修改原始对象的属性,而是先进入 handler.set 拦截器:
set(target, key, val, receiver) {
// 第一步:执行自定义逻辑(打印日志)
console.log('拦截设置属性:', key, '=', val); // 输出:拦截设置属性:name = 李四
// 第二步:调用 Reflect.set 执行「原始赋值逻辑」
// 等价于直接修改原始对象:target[key] = val(但更规范、更安全)
return Reflect.set(target, key, val, receiver);
}
- 执行
Reflect.set(target, 'name', '李四', receiver):会把原始对象的name属性值从张三改为李四,并返回布尔值(表示赋值是否成功)。 - 最终效果:原始对象的
name属性被正确修改,同时我们的自定义拦截逻辑(打印日志)也执行了。
4.2.3. 执行 console.log(obj.name)(触发 get 拦截器)
执行属性读取操作时,不会直接读取原始对象的属性,而是先进入 handler.get 拦截器:
get(target, key, receiver) {
// 第一步:执行自定义逻辑(打印日志)
console.log('拦截获取属性:', key); // 输出:拦截获取属性:name
// 第二步:调用 Reflect.get 执行「原始读取逻辑」
// 等价于直接读取原始对象:target[key](但更规范、支持 receiver 绑定)
return Reflect.get(target, key, receiver);
}
- 执行
Reflect.get(target, 'name', receiver):会读取原始对象的name属性值(此时已经是李四),并返回这个值。 - 最终效果:控制台先打印日志,再输出属性值
李四。
四、Reflect 的典型应用场景
- Vue3 响应式底层:Vue3 的
reactive基于Proxy拦截对象操作,内部通过Reflect.get/set完成属性的读取 / 赋值,并利用receiver绑定正确的this,保证嵌套对象、继承属性的响应式正常; - 自定义对象拦截器:封装复杂的对象操作逻辑(如权限校验、日志记录)时,用
Reflect替代传统操作,代码更健壮、易维护; - 函数式编程:将对象操作转为函数调用,便于组合、柯里化等函数式技巧;
- 兼容旧代码:统一处理对象操作的成功 / 失败,避免大量 try/catch。
五、关键总结
Reflect是 ES6 为 “对象底层操作” 提供的标准化 API,非新增功能,而是对原有操作的封装;- 核心优势:结果标准化(返回布尔值)、函数式调用、支持 this 绑定、与 Proxy 深度配合;
- 日常开发中直接使用场景不多,但理解它是掌握 Vue3 响应式、Proxy 拦截的关键;
- 对比
Object:Reflect更关注 “操作行为”,Object更关注 “对象本身的属性 / 方法管理”。