Reflect API

Reflect对象是一个普通对象(就像Math),不是其他内建原生类型那样的函数/构造器。

它持有对应于你可以控制的各种元编程任务的静态函数。这些函数与代理可以定义的处理器方法(机关)一一对应。

这些函数中的一些看起来与在Object上的同名函数很相似:

  • Reflect.getOwnPropertyDescriptor(..)
  • Reflect.defineProperty(..)
  • Reflect.getPrototypeOf(..)
  • Reflect.setPrototypeOf(..)
  • Reflect.preventExtensions(..)
  • Reflect.isExtensible(..)

这些工具一般与它们的Object.*对等物的行为相同。但一个区别是,Object.*对等物在它们的第一个参数值(目标对象)还不是对象的情况下,试图将它强制转换为一个对象。Reflect.*方法在同样的情况下仅简单地抛出一个错误。

一个对象的键可以使用这些工具访问/检测:

  • Reflect.ownKeys(..):返回一个所有直属(不是“继承的”)键的列表,正如被 Object.getOwnPropertyNames(..)Object.getOwnPropertySymbols(..)返回的那样。关于键的顺序问题,参见“属性枚举顺序”一节。
  • Reflect.enumerate(..):返回一个产生所有(直属和“继承的”)非symbol、可枚举的键的迭代器(参见本系列的 this与对象原型)。 实质上,这组键与在for..in循环中被处理的那一组键是相同的。关于键的顺序问题,参见“属性枚举顺序”一节。
  • Reflect.has(..):实质上与用于检查一个属性是否存在于一个对象或它的[[Prototype]]链上的in操作符相同。例如,Reflect.has(o,"foo")实质上实施"foo" in o

函数调用和构造器调用可以使用这些工具手动地实施,与普通的语法(例如,(..)new)分开:

  • Reflect.apply(..):例如,Reflect.apply(foo,thisObj,[42,"bar"])使用thisObj作为foo(..)函数的this来调用它,并传入参数值42"bar"
  • Reflect.construct(..):例如,Reflect.construct(foo,[42,"bar"])实质上调用new foo(42,"bar")

对象属性访问,设置,和删除可以使用这些工具手动实施:

  • Reflect.get(..):例如,Reflect.get(o,"foo")会取得o.foo
  • Reflect.set(..):例如,Reflect.set(o,"foo",42)实质上实施o.foo = 42
  • Reflect.deleteProperty(..):例如,Reflect.deleteProperty(o,"foo")实质上实施delete o.foo

Reflect的元编程能力给了你可以模拟各种语法特性的程序化等价物,暴露以前隐藏着的抽象操作。例如,你可以使用这些能力来扩展 领域特定语言(DSL)的特性和API。

属性顺序

在ES6之前,罗列一个对象的键/属性的顺序没有在语言规范中定义,而是依赖于具体实现的。一般来说,大多数引擎会以创建的顺序来罗列它们,虽然开发者们已经被强烈建议永远不要依仗这种顺序。

在ES6中,罗列直属属性的属性是由[[OwnPropertyKeys]]算法定义的(ES6语言规范,9.1.12部分),它产生所有直属属性(字符串或symbol),不论其可枚举性。这种顺序仅对Reflect.ownKeys(..)有保证()。

这个顺序是:

  1. 首先,以数字上升的顺序,枚举所有数字索引的直属属性。
  2. 然后,以创建顺序枚举剩下的直属字符串属性名。
  3. 最后,以创建顺序枚举直属symbol属性。

考虑如下代码:

  1. var o = {};
  2. o[Symbol("c")] = "yay";
  3. o[2] = true;
  4. o[1] = true;
  5. o.b = "awesome";
  6. o.a = "cool";
  7. Reflect.ownKeys( o ); // [1,2,"b","a",Symbol(c)]
  8. Object.getOwnPropertyNames( o ); // [1,2,"b","a"]
  9. Object.getOwnPropertySymbols( o ); // [Symbol(c)]

另一方面,[[Enumeration]]算法(ES6语言规范,9.1.11部分)从目标对象和它的[[Prototype]]链中仅产生可枚举属性。它被用于Reflect.enumerate(..)for..in。可观察到的顺序是依赖于具体实现的,语言规范没有控制它。

相比之下,Object.keys(..)调用[[OwnPropertyKeys]]算法来得到一个所有直属属性的列表。但是,它过滤掉了不可枚举属性,然后特别为了JSON.stringify(..)for..in而将这个列表重排,以匹配遗留的、依赖于具体实现的行为。所以通过扩展,这个顺序 Reflect.enumerate(..)的顺序像吻合。

换言之,所有四种机制(Reflect.enumerate(..)Object.keys(..)for..in,和JSON.stringify(..))都同样将与依赖于具体实现的顺序像吻合,虽然技术上它们是以不同的方式达到的同样的效果。

具体实现可以将这四种机制与[[OwnPropertyKeys]]的顺序相吻合,但不是必须的。无论如何,你将很可能从它们的行为中观察到以下的排序:

  1. var o = { a: 1, b: 2 };
  2. var p = Object.create( o );
  3. p.c = 3;
  4. p.d = 4;
  5. for (var prop of Reflect.enumerate( p )) {
  6. console.log( prop );
  7. }
  8. // c d a b
  9. for (var prop in p) {
  10. console.log( prop );
  11. }
  12. // c d a b
  13. JSON.stringify( p );
  14. // {"c":3,"d":4}
  15. Object.keys( p );
  16. // ["c","d"]

这一切可以归纳为:在ES6中,根据语言规范Reflect.ownKeys(..)Object.getOwnPropertyNames(..),和Object.getOwnPropertySymbols(..)保证都有可预见和可靠的顺序。所以依赖于这种顺序来建造代码是安全的。

Reflect.enumerate(..)Object.keys(..),和for..in (扩展一下的话还有JSON.stringify(..))继续互相共享一个可观察的顺序,就像它们往常一样。但这个顺序不一定与Reflect.ownKeys(..)的相同。在使用它们依赖于具体实现的顺序时依然应当小心。