Reflect 是 ES6 引入的一个内置对象,它把对象上一些分散的内部方法集中起来,提供了一套统一的 API 用于对象操作。和 Math 类似,Reflect 不是一个构造函数,不能通过 new 来调用,它的所有属性和方法都是静态的。
说实话,刚开始接触 Reflect 的时候我也挺困惑的——这些操作明明用现有的语法就能完成,比如 obj[key] 或者 delete obj.prop,干嘛还要多此一举用 Reflect?后来在项目里踩了几次坑才慢慢理解,Reflect 的设计背后有一套完整的元编程逻辑在支撑。
Reflect.apply() —— 函数调用的函数式写法
方法签名: Reflect.apply(target, thisArgument, argumentsList)
这个方法用来调用一个函数,同时可以指定函数内部的 this 指向。它的作用和 Function.prototype.apply() 基本一致,但在某些边界情况下行为更可预测。
为什么不用 func.apply() 而用 Reflect.apply()?
有一个不太为人知的坑:Function.prototype.apply 是可以被重写的。假如某个代码库把 Function.prototype.apply 改写了,或者你操作的对象根本不是函数(比如它是一个 Proxy 代理),直接调用 func.apply() 就可能导致不可预期的结果。Reflect.apply 作为语言层面的底层方法,行为更可靠。
// 示例:计算班级平均分的工具函数
function calculateAverage(...scores) {
const sum = scores.reduce((acc, cur) => acc + cur, 0);
return sum / scores.length;
}
// 传统写法
const result1 = calculateAverage.apply(null, [85, 92, 78, 90]);
// Reflect 写法
const result2 = Reflect.apply(calculateAverage, null, [85, 92, 78, 90]);
console.log(result2); // 86.25
// 进阶用法:动态切换上下文
const scoreCalculator = {
baseScore: 10,
calculate(scores) {
return scores.map(s => s + this.baseScore);
}
};
const context = { baseScore: 5 };
const adjustedScores = Reflect.apply(scoreCalculator.calculate, context, [[80, 90, 85]]);
console.log(adjustedScores); // [85, 95, 90]
本节课程知识要点: 当你的代码需要在严格模式下确保函数调用的行为一致性,或者在与 Proxy 配合使用时,优先考虑 Reflect.apply。它返回的值和函数本身的返回值相同,不会有额外的包装。
Reflect.construct() —— 构造实例的另一种思路
方法签名: Reflect.construct(target, argumentsList[, newTarget])
这个方法相当于 new target(...args),但提供了第三个可选参数 newTarget。如果传入了 newTarget,实际创建的实例会继承 newTarget.prototype,但构造函数本身的逻辑仍然由 target 执行。
这个特性在实现某些设计模式时相当好用,比如装饰器模式或者类工厂。
为什么不用 new 关键字?
new 运算符是硬编码在语法层面的,你没法把它当参数传递,也没法动态决定“用哪个构造函数创建实例但继承另一个类的原型”。Reflect.construct 把这层控制权交还给了开发者。
// 示例:编程学习平台的课程类
class ProgrammingCourse {
constructor(name, level) {
this.courseName = name;
this.difficulty = level;
this.enrolledAt = '2026年';
}
getDescription() {
return `${this.courseName} - ${this.difficulty}级别`;
}
}
class AdvancedCourse {
constructor() {
this.certificate = true;
this.mentorSupport = '包含导师辅导';
}
}
// 使用 Reflect.construct 创建实例
const jsCourse = Reflect.construct(ProgrammingCourse, ['JavaScript深入解析', '进阶']);
console.log(jsCourse.getDescription()); // JavaScript深入解析 - 进阶级别
// 使用第三个参数:用 ProgrammingCourse 的逻辑创建实例,但继承 AdvancedCourse 的原型
const hybridCourse = Reflect.construct(
ProgrammingCourse,
['全栈开发实战', '专家'],
AdvancedCourse
);
console.log(hybridCourse.courseName); // 全栈开发实战(来自 ProgrammingCourse)
console.log(hybridCourse.certificate); // true(来自 AdvancedCourse.prototype)
console.log(hybridCourse instanceof AdvancedCourse); // true
console.log(hybridCourse instanceof ProgrammingCourse); // false
本节课程知识要点: Reflect.construct 的第三个参数在实现继承变体时很有价值。如果省略 newTarget,默认使用 target 自身。这个方法抛出异常的情况和 new 一致——如果 target 不可构造,会抛出 TypeError。
Reflect.defineProperty() —— 带明确反馈的属性定义
方法签名: Reflect.defineProperty(target, propertyKey, attributes)
这个方法对应 Object.defineProperty(),功能上一致,但返回值不同。Object.defineProperty 在成功时返回传入的对象,失败时抛出 TypeError;而 Reflect.defineProperty 返回一个布尔值表示操作是否成功。
这个差异在开发中影响不小。抛出异常会打断代码执行流,而返回布尔值让你可以用条件判断来处理失败情况,代码的容错性和可读性都更好。
// 示例:配置编程项目的文件对象
const codeFile = {
filename: 'index.js',
content: ''
};
// Object.defineProperty 的方式 —— 失败会抛异常,需要用 try-catch 包裹
try {
Object.defineProperty(codeFile, 'filename', {
writable: false,
configurable: false
});
console.log('属性定义成功');
} catch (error) {
console.log('定义失败:', error.message);
}
// Reflect.defineProperty 的方式 —— 返回布尔值,代码更简洁
const defineSuccess = Reflect.defineProperty(codeFile, 'size', {
value: 2048,
writable: true,
enumerable: true,
configurable: true
});
if (defineSuccess) {
console.log(`文件 ${codeFile.filename} 大小设置为 ${codeFile.size} 字节`);
} else {
console.log('属性定义未成功');
}
// 尝试重新定义一个不可配置的属性 —— 静默失败,返回 false
const redefineResult = Reflect.defineProperty(codeFile, 'filename', {
writable: true
});
console.log(redefineResult); // false
console.log(codeFile.filename); // 'index.js',未被修改
本节课程知识要点: 在处理第三方库或者不确定对象是否可扩展的场景下,Reflect.defineProperty 的布尔返回值让错误处理更符合函数式编程的习惯。配合 Proxy 的 defineProperty 时,也应该返回布尔值以符合规范,这时用 Reflect.defineProperty 是最自然的选择。
Reflect 方法一览表
| 方法名 | 功能描述 | 对应传统操作 |
|---|---|---|
Reflect.apply() |
调用函数并指定 this 和参数 | Function.prototype.apply() |
Reflect.construct() |
调用构造函数创建实例 | new 运算符 |
Reflect.defineProperty() |
定义或修改对象属性 | Object.defineProperty() |
Reflect.deleteProperty() |
删除对象属性 | delete 运算符 |
Reflect.get() |
读取对象属性值 | obj[prop] 或点运算符 |
Reflect.set() |
设置对象属性值 | obj[prop] = value |
Reflect.getOwnPropertyDescriptor() |
获取属性描述符 | Object.getOwnPropertyDescriptor() |
Reflect.getPrototypeOf() |
获取对象原型 | Object.getPrototypeOf() |
Reflect.setPrototypeOf() |
设置对象原型 | Object.setPrototypeOf() |
Reflect.has() |
检查属性是否存在 | in 运算符 |
Reflect.isExtensible() |
检查对象是否可扩展 | Object.isExtensible() |
Reflect.preventExtensions() |
阻止对象扩展 | Object.preventExtensions() |
Reflect.ownKeys() |
获取对象自身的所有键 | Object.getOwnPropertyNames() + Object.getOwnPropertySymbols() |
Reflect 的价值不在于它提供了“新功能”,而在于它把分散在各处的对象操作方法统一到了一个命名空间下,并且让这些方法的行为更符合函数式编程的预期(返回值统一、不抛异常)。如果你正在使用 ES6 的 Proxy,那么 Reflect 几乎是必备搭档——Proxy 的每个拦截器都有对应的 Reflect 方法作为默认行为的“回退”选项。
我个人在写工具库的时候会更倾向于用 Reflect,尤其是在需要兼容多种运行环境时,它能避免很多因为原型链被篡改而引发的诡异 bug。日常业务代码里用不用看团队习惯,两种写法没有绝对的对错之分。