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(..)
有保证()。
这个顺序是:
- 首先,以数字上升的顺序,枚举所有数字索引的直属属性。
- 然后,以创建顺序枚举剩下的直属字符串属性名。
- 最后,以创建顺序枚举直属symbol属性。
考虑如下代码:
var o = {};
o[Symbol("c")] = "yay";
o[2] = true;
o[1] = true;
o.b = "awesome";
o.a = "cool";
Reflect.ownKeys( o ); // [1,2,"b","a",Symbol(c)]
Object.getOwnPropertyNames( o ); // [1,2,"b","a"]
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]]
的顺序相吻合,但不是必须的。无论如何,你将很可能从它们的行为中观察到以下的排序:
var o = { a: 1, b: 2 };
var p = Object.create( o );
p.c = 3;
p.d = 4;
for (var prop of Reflect.enumerate( p )) {
console.log( prop );
}
// c d a b
for (var prop in p) {
console.log( prop );
}
// c d a b
JSON.stringify( p );
// {"c":3,"d":4}
Object.keys( p );
// ["c","d"]
这一切可以归纳为:在ES6中,根据语言规范Reflect.ownKeys(..)
,Object.getOwnPropertyNames(..)
,和Object.getOwnPropertySymbols(..)
保证都有可预见和可靠的顺序。所以依赖于这种顺序来建造代码是安全的。
Reflect.enumerate(..)
,Object.keys(..)
,和for..in
(扩展一下的话还有JSON.stringification(..)
)继续互相共享一个可观察的顺序,就像它们往常一样。但这个顺序不一定与Reflect.ownKeys(..)
的相同。在使用它们依赖于具体实现的顺序时依然应当小心。