面试官 : “ 说一下 ES6 新特性 Reflect 是什么? ”

58 阅读6分钟

在 ES6 中,Reflect 是一个内置的静态对象(不能实例化,类似 Math/Object),它将对象的一些底层操作方法(如属性访问、赋值、删除、函数调用等)规范化、模块化地封装起来,弥补了 Object 原有方法的设计缺陷,同时与 Proxy 形成完美配合,是 ES6 响应式(如 Vue3 响应式)的核心底层支撑之一。

一、核心设计目的

  1. 统一对象操作 API:将分散在 Objectin 运算符、delete 运算符等的底层操作,集中到 Reflect 上,语义更清晰;
  2. 函数化替代命令式操作:把 indelete 等命令式操作转为函数调用,更易组合、复用;
  3. 修正 Object 方法的怪异行为Object 部分方法失败时抛异常,Reflect 统一返回布尔值表示操作结果;
  4. 能够在 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. 函数化替代命令式操作

把 indelete 等运算符转为函数,便于动态调用 / 封装:

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 → 输出 李四

控制台输出如下

image.png

这段代码是 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.nameobj['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 的典型应用场景

  1. Vue3 响应式底层:Vue3 的 reactive 基于 Proxy 拦截对象操作,内部通过 Reflect.get/set 完成属性的读取 / 赋值,并利用 receiver 绑定正确的 this,保证嵌套对象、继承属性的响应式正常;
  2. 自定义对象拦截器:封装复杂的对象操作逻辑(如权限校验、日志记录)时,用 Reflect 替代传统操作,代码更健壮、易维护;
  3. 函数式编程:将对象操作转为函数调用,便于组合、柯里化等函数式技巧;
  4. 兼容旧代码:统一处理对象操作的成功 / 失败,避免大量 try/catch。

五、关键总结

  1. Reflect 是 ES6 为 “对象底层操作” 提供的标准化 API,非新增功能,而是对原有操作的封装;
  2. 核心优势:结果标准化(返回布尔值)、函数式调用、支持 this 绑定、与 Proxy 深度配合
  3. 日常开发中直接使用场景不多,但理解它是掌握 Vue3 响应式、Proxy 拦截的关键;
  4. 对比 ObjectReflect 更关注 “操作行为”,Object 更关注 “对象本身的属性 / 方法管理”。