「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战」
Reflect 是一个内置对象,提供了一系列操作对象的方法,这些方法都是静态的(类似 Math),使用 Reflect 的方法可以做一些对象的基本操作。
Reflect 意思是反射,反射是在程序运行中获取和动态操作自身内容的一项技术。其实对于 JavaScript 来说在出现 Reflect 之前就已经有反射的能力了,我们可以使用 Object.keys() 获取对象属性,可以使用 Object.defineProperty 定义属性,这些实际上都是反射的能力。既然已经可以实现反射功能,那为什么还需要有 Reflect 呢?
为什么要有 Reflect
来看一个真实的使用场景,ext 是一个普通对象,它的全部属性信息,使用 Object 应该怎样做呢?
看起来很简单是吧?可以这样:
Object.keys(ext);
或者这样:
Object.getOwnPropertyNames(ext);
然而这样获取到的只是字符串属性,对象的属性只能是字符串吗?并不是,除了字符串,Symbol 类型也可以作为对象的属性 key,因此上面的代码应该改成这样:
Object.getOwnPropertyNames(ext)
.concat(Object.getOwnPropertySymbols(ext));
这是一个在开源框架中的常见用法: github.com/eggjs/egg-c…。
如果有了 Reflect,上面的代码只需要这样:
Reflect.ownKeys(ext);
效果是完全相同的,但是语义更加明确。
上面这个例子可能不够直观,你可以说这不是就是增加了一个新的 API 吗?那我们再看一个例子:
使用 Object.defineProperty 为对象添加属性,如果失败了如何处理呢?
由于 Object.defineProperty 失败是会直接抛一个异常,因此代码应该是这样的:
try {
Object.defineProperty(obj, prop, attr);
// success
} catch (e) {
// fail
}
使用 Reflect 代码就变成下面这样:
if (Reflect.defineProperty(obj, prop, attr)) {
// success
} else {
// fail
}
看起来区别不大,但是哪一种更合理呢?反射一种编程语言的行为,我执行反射做什么我只需要知道是否成功就可以了,我可能希望核心流程被异常打断。反过来说如果我想要异常我可以自己判断在失败时抛出异常,而不是你直接给我一个异常我原本处理失败的逻辑现在都要写 catch 块中。因此 Reflect 这种实现和 Object 相比提供了更完整的能力。
再比如删除属性,在不使用 Reflect 时需要使用 delete 关键字,而使用 Reflect 可以调用上面的 deleteProperty 方法来实现功能,其中的区别在实际开发中可以有更明显的感受。
Reflect 提供的是一整套反射能力 API,它们的调用方式,参数和返回值都是统一风格的,我们可以使用 Reflect 写出更优雅的反射代码。在 Reflect 库出现之前我们只是利用分散的 API 完成一些动态编程的事情,Reflect 使得反射真正成为一种标准化能力。
Reflect 相关方法
Reflect 上面的方法:
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
这些方法其实都很简单,通过名字也很容易知道方法的定义,大部分也都在 Object 上面见到过,Reflect 与 Object 部分重复方法详细的区别可以查看 MDN
Reflect 的应用
proxy
Reflect 对象上面的方法和 Proxy 上面都是一一对应的,参数也是完全相同的,因此在 Proxy 中可以使用 Reflect 来操作对象:
new Proxy(obj, {
set(...args) {
return Reflect.set(...args)
},
get(...args) {
return Reflect.get(...args);
},
deleteProperty(...args) {
return Reflect.deleteProperty(...args);
},
has(...args) {
return Reflect.has(...args);
}
}
metadata
Reflect 更强大之处在于它的元编程能力,有一个 metadata 的提案可以通过 Reflect 添加元数据信息,使用元数据编程能够更方便进行框架开发,metadata 的提案目前还在 stage 阶段,目前可以通过 github.com/rbuckton/re… 库来提前使用相关功能,在 typescript 中还可以使用 emitDecoratorMetadata 的增强功能,感兴趣可以阅读。