本文由 简悦SimpRead 转码,原文地址 medium.com
在本课中,我们将学习ES2015+中的 "Reflect "全局对象。它......
在这一课中,我们将学习ES2015+中的Reflect全局对象。它提供了各种静态函数来反观和修改JavaScript对象。
(source: unsplash.com)
正如我们在早期课程中所学到的,反思代表了对程序的反省、干涉和修改。在ES2015(ES6)之前,我们有一些工具可以用来内省和修改程序的行为,如Object.keys或instanceof运算符等。
在ES2015中,我们收到了一个Reflect全局对象,为元编程提供了一些相当有用的方法。像Math和JSON对象一样,Reflect不是一个函数,也不能被构造。它的唯一工作是为反射提供静态方法。这些方法可以分为两类。
内省方法是非破坏性的方法。它们只用于反省对象。修改方法是破坏性的,因为它们改变了对象或其行为。这些方法中的大部分都来自于老的JavaScript实现,我们也会讨论这个问题。
在早期课程中,我们快速浏览了Proxy类,它被用来拦截对象操作。Proxyhandler方法和Reflect静态方法有相同的函数签名。我们将在Proxy一课中更多地讨论这个问题(即将到来),但让我们简单地讨论一下为什么这些方法的签名是一样的。为此,我们需要看看ECMAScript的规范里面。
对象内部方法和内部槽
ECMAScript 2015规范的6.1.7.2部分谈到了一些奇怪的内部属性和内部方法,对象( Object 的后裔)可以有。这些属性或方法是由JavaScript引擎实现的,但它们被抽象出来,不在运行时使用,因此你不能像普通属性那样在对象上访问它们。
在ECMAScript规范中,这些都是用[[<name>]]符号表示的,其中 "name "是内部属性或内部方法的名称。关于这个话题,我写了一篇简短的文章(下面提到的),请在继续阅读本文之前先看一下。
每次你调用Reflect的静态方法,一些内部方法就会被执行,或者访问target对象上的一些内部槽,返回结果或改变target对象的行为。Proxy处理方法执行相同的内部方法,因此Proxy处理方法的方法签名和Reflect的静态方法是相同的。
这些方法纯粹用于自省,它们不会修改对象、其行为或其内部状态。
自省方法
● Reflect.get
Reflect.get(target, propertyKey[, receiver])
当调用Reflect.get方法时,如果有target和propertyKey,则返回target中propertyKey属性的值。如果名称为propertyKey的属性是target中的getter函数,receiver参数将作为this使用。如果缺少receiver,this就是target。
Reflect.get()调用类似于target[propertyKey]表达式,因为它也是在target的原型上搜索属性值。如果一个属性在target上不存在,就会返回undefined。
💡 你也可以在数组上使用
_Reflect.get_(当_target_是一个_Array_),使用_propertyKey_作为_number_或_string_的索引。
这个方法在内部调用对象的[[Get]]内部方法,使用propertyKey和receiver在目标上,该目标是参数[ref]中收到的target。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.get( null, 'prop' )
Reflect.get( 'hello', 'length' )
// TypeError: Reflect.get called on non-object
● Reflect.has
Reflect.has(target, propertyKey)
Reflect.has方法检查target对象或其原型上是否存在propertyKey属性。这与in操作符的工作原理完全相同。如果找到该属性,它将返回true,否则将返回false。这意味着一个类的实例上的方法名也会返回true(因为它是一个继承的属性)。
💡 你也可以在数组上使用
_Reflect.has_(当_target_是一个_Array_),使用_propertyKey_作为_number_或_string_索引。
这个方法在内部调用对象的[[HasProperty]]内部方法,目标是参数[ref]中收到的target的propertyKey。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.ownKeys( null )
Reflect.ownKeys( 'hello' )
// TypeError: Reflect.ownKeys called on non-object
💡 如果你想检查一个对象是否对它自己而不是对它的原型有一个属性,那么用Object.hasOwnProperty方法代替。
● Reflect.ownKeys
Reflect.ownKeys(target)
Reflect.ownKeys方法返回一个包含目标对象的自有属性的数组。与Object.keys不同的是,它还包括非枚举的属性。它也包括符号属性。因此,返回值相当于Object.getOwnPropertyNames(target)和Object.getOwnPropertySymbols(target)的组合结果。
这个方法在内部调用对象的[[OwnPropertyKeys]]内部方法,这个对象是在参数[ref]中收到的target。因此,如果提供了一个非对象的target(primitive value),这个方法的调用将抛出一个TypeError异常。
Reflect.ownKeys( null )
Reflect.ownKeys( 'hello' )
// TypeError: Reflect.ownKeys called on non-object
● Reflect.getOwnPropertyDescriptor
Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.getOwnPropertyDescriptor方法返回target对象的非继承属性propertyKey的property descriptor 。如果该属性在target上不存在,将返回undefined。
(reflect/getOwnPropertyDescriptor.js)
💡 你也可以在数组上使用
_Reflect.getOwnPropertyDescriptor_(当_target_是一个_Array_),使用_propertyKey_作为_number_或_string_索引。
这个方法在内部调用[[GetOwnProperty]]对象的内部方法,目标是参数[ref]中收到的target,具有propertyKey。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.getOwnPropertyDescriptor( null, 'prop' )
Reflect.getOwnPropertyDescriptor( 'hello', 'length' )
// TypeError: Reflect.getOwnPropertyDescriptor called on non-object
我们也有一个类似的Object.getOwnPropertyDescriptor方法,返回目标的属性描述符,但是在这种情况下,如果目标不是一个对象。它将被强制为一个对象(在ES2015中),使用它的构造函数(如 "hey"变成String("hey") ),但null和undefined值除外(因为它们没有公共构造函数)。
Object.getOwnPropertyDescriptor( null )
// TypeError: Cannot convert undefined or null to object
Object.getOwnPropertyDescriptor( 'hello', 'length' )
// {value: 5, writable: false, enumerable: false, …}
Object.getOwnPropertyDescriptor( {}, 'prop' )
// undefined
● Reflect.getPrototypeOf
Reflect.getPrototypeOf(target)
Reflect.getPrototypeOf方法返回target对象的原型。如果target没有原型,将返回null。
这个方法在内部调用对象的[[GetPrototypeOf]]内部方法,这个对象是在参数[ref]中收到的target。因此,如果提供了一个非对象的target(primitive value),这个方法的调用将抛出一个TypeError异常。
Reflect.getPrototypeOf( null )
Reflect.getPrototypeOf( 'hello' )
// TypeError: Reflect.getPrototypeOf called on non-object
我们也有一个类似的Object.getPrototypeOf方法,返回目标的原型,但在这种情况下,如果目标不是一个对象,它将被强制为一个对象(在ES2015中),使用其构造函数,除了null和undefined值(因为它们没有公共构造函数)。
Object.getPrototypeOf( null )
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf( 'hello' )
// String {constructor: String(), anchor: ƒ, big: ƒ, …}
Object.getPrototypeOf( {} )
// Object {constructor: Object(), __defineGetter__: ƒ, …}
● Reflect.isExtensible
Reflect.isExtensible(target)
Reflect.isExtensible方法用来检查target对象是否是可扩展的。如果target是可扩展的,也就是说,如果可以向它添加新的属性,这个方法返回true,否则返回false。
Object.preventExtenstions()方法阻止你向对象添加任何新的属性,但是,你可以对对象进行任何其他操作。Object.freeze()和Object.seal()方法也使对象不可扩展。
这个方法在内部调用对象的[[IsExtensible]]内部方法,这个目标是参数[ref]中收到的目标。因此,如果提供了一个非对象的target(primitive value),这个方法的调用将抛出一个TypeError异常。
Reflect.isExtensible( null )
Reflect.isExtensible( 'hello' )
// TypeError: Reflect.isExtensible called on non-object
我们也有一个类似的Object.isExtensible方法,返回目标的原型,但在这种情况下,如果目标不是一个对象,就会返回false而不是抛出一个类型错误。
Object.isExtensible( null )
// false
Object.isExtensible( 'hello' )
// false
Object.isExtensible( {} )
// true
我知道你会说,离开Reflect.isExtensible,我将使用Object.isExtensible方法,因为它不会抛出一个错误。好吧,这是更大问题的一部分,因为你需要对传入的target值进行类型检查,以确定false的返回是因为target不可扩展还是因为它是一个原始值。
这些方法修改了target值或其行为,或其内部状态。
Modification Methods
● Reflect.deleteProperty
Reflect.deleteProperty(target, propertyKey)
Reflect.deleteProperty方法从target对象中删除属性propertyKey。如果属性被成功地从target中删除,则返回true,否则返回false。这相当于使用delete操作符。
和delete操作符一样,如果属性propertyKey不存在,它将总是返回true。这个方法不会删除target的原型链上的属性,只有自己的属性可以被删除。另外,不可配置的属性不能被删除。
💡 你也可以在数组上使用
_Reflect.deleteProperty_(当_target_是一个_Array_时),使用_propertyKey_作为_number_或_string_索引。
这个方法在内部调用对象的[[Delete]]内部方法,目标上的propertyKey是参数[ref]中收到的target。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.deleteProperty( null, 'prop' )
Reflect.deleteProperty( 'hello', 'length' )
// TypeError: Reflect.deleteProperty called on non-object
● Reflect.set
Reflect.set(target, propertyKey, value[, receiver])
Reflect.set的工作方式与Reflect.get类似,但它不是检索target的propertyKey属性的值,而是用参数中收到的value更新target自己的属性值。如果一个名称为propertyKey的属性已经存在,其值将被更新,否则将创建一个新的属性。
如果属性已经存在并且有一个setter函数,如果提供的话,setter函数里面的this值将是receiver,否则将使用target。这个方法等同于使用target[_propertyKey_] = value表达式。
💡 你也可以在数组上使用
Reflect.set(当target是一个Array时),使用propertyKey作为number或string索引。
这个方法在内部调用对象的[[Set]]内部方法,其中有propertyKey, value和receiver,目标是参数[ref]中收到的target。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.set( null, 'prop', 'val' )
Reflect.set( 'hello', 'length', 2 )
// TypeError: Reflect.set called on non-object
● Reflect.defineProperty
Reflect.defineProperty(target, propertyKey, descriptor)
Reflect.defineProperty在target对象上创建一个新的属性,名称为propertyKey,descriptor是属性描述符。
如果该属性已经存在,只有属性描述符会被更新。如果在descriptor中只提供了一些属性描述符字段,那么只有这些字段将被更新。如果属性被成功设置,该方法返回true,否则返回false。
💡 你也可以在数组上使用
_Reflect.defineProperty_(当_target_是一个_Array_),使用_propertyKey_作为_number_或_string_索引。
这个方法在内部调用对象的[[DefineOwnProperty]]内部方法,并在目标上使用propertyKey和descriptor,该目标是参数[ref]中收到的target。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.defineProperty( **null**, 'prop', {value: 2})\
Reflect.defineProperty( **'hello'** , 'length', {value: 3})\
*// TypeError: Reflect.defineProperty called on non-object*
我们也有一个类似的Object.defineProperty方法,做的事情完全一样。但是这个方法返回更新的target对象,而不是返回一个boolean值。另外,如果属性不能在 "目标 "上定义,它会抛出 "类型错误 "异常,而不是返回 "假"。
● Reflect.preventExtensions
Reflect.preventExtensions(target)
Reflect.preventExtensions方法使target对象不可扩展。一旦一个对象是不可扩展的,就不能向该对象添加新的属性。如果该对象被制成不可扩展,该方法返回true,否则返回false。
(reflect/preventExtensions.js)
这个方法在内部调用对象的[[PreventExtensions]]内部方法,该对象是参数[ref]中收到的target。因此,如果提供了一个非对象的 "目标"(primitive value),这个方法的调用将抛出一个 "类型错误 "异常。
Reflect.preventExtensions( null )
Reflect.preventExtensions( 'hello' )
// TypeError: Reflect.preventExtensions called on non-object
我们也有一个类似的Object.preventExtensions方法,做的是完全相同的事情。但是这个方法返回更新的target对象,而不是返回一个boolean值。如果target不是一个对象,则返回原始值。
Object.preventExtensions( null )
// false
Object.preventExtensions( 'hello' )
// 'hello'
Object.preventExtensions( {name: 'ross'} )
// {name: 'ross'}
● Reflect.setPrototypeOf
Reflect.setPrototypeOf(target, prototype)
Reflect.setPrototypeOf方法在target对象上设置prototype原型。如果成功地在target上设置了原型,它将返回true,否则将返回false。如果target是不可扩展的,原型将不会被设置,false会被返回。
var obj = Object.preventExtensions({});
Reflect.setPrototypeOf(obj, null);
// false
如果prototype不是一个有效的Object或null,那么就会抛出一个TypeError。
Reflect.setPrototypeOf({}, "")\
Reflect.setPrototypeOf(function(){}, "")\
*// TypeError: Object prototype may only be an Object or null*
这个方法在内部调用[[SetPrototypeOf]]对象的内部方法,目标是参数[ref]中收到的target,具有prototype。因此,如果提供了一个非对象的target(primitive value),这个方法调用将抛出一个TypeError异常。
Reflect.setPrototypeOf( **null**, null )\
Reflect.setPrototypeOf( **'hello'** , null )\
*// TypeError: Reflect.setPrototypeOf called on non-object*
我们也有一个类似的Object.setPrototypeOf方法,做的事情完全一样。但是这个方法返回更新的target对象,而不是返回一个boolean值。如果 "target "不是 "Object "或 "null",或者 "target "是不可扩展的,将抛出一个 "类型错误"。
Object.setPrototypeOf( null, null )
// TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf( Object.preventExtensions({}), null )
// ❌ TypeError: #<Object> is not extensible
Object.setPrototypeOf( {name: 'ross'}, "")
// TypeError: Object prototype may only be an Object or null
Object.setPrototypeOf( {name: 'ross'}, null )
// {name: 'ross'}
● Reflect.apply
Reflect.apply(target, thisArgument, argumentsList)
Reflect.apply并不完全是一个修改方法。这个方法调用一个函数,这个函数是带有argumentsList参数数组的target,并提供thisArgument作为函数调用的this值。
这相当于调用apply目标的原型方法(如果_目标_是一个函数),但有一些注意事项。如果 "argumentsList "是 "空 "或 "未定义","Reflect.apply "将抛出一个 "类型错误",而 "apply "原型方法将在没有任何参数的情况下调用该函数。
let func = ( ...args ) => console.log(args);
func.apply(null, undefined)
func.apply(null, null)
func.apply(null, {})
// []
func.apply(null, "")
// TypeError: CreateListFromArrayLike called on non-object
▶ func.apply(null, [1, 2, 3])
// [1, 2, 3]
Reflect.apply(func, null, undefined)
Reflect.apply(func, null, null)
Reflect.apply(func, null, "")
// TypeError: CreateListFromArrayLike called on non-object
Reflect.apply(func, null, {})
// []
▶ Reflect.apply(func, null, [1, 2, 3])
// [1, 2, 3]
这个方法在内部调用函数的[[Call]]内部方法,并在目标上使用thisArgument和argumentsList,该目标是参数[ref]中收到的target。因此,如果提供了一个非函数的target,这个方法调用将抛出一个TypeError异常。
Reflect.apply( null, null, [] )
// TypeError: Function.prototype.apply was called on null, which is a object and not a function
Reflect.apply( 'hello', null, [] )
// TypeError: Function.prototype.apply was called on hello, which is a string and not a function
Reflect.apply( {}, null, [] )
// TypeError: Function.prototype.apply was called on #<Object>, which is a object and not a function
● Reflect.construct
Reflect.construct(target, argumentsList[, newTarget])
Reflect.construct方法从target构造函数(class)构造一个对象。这相当于使用new操作符和argumentsList,如new target(..._argumentsList_)。
newTarget也是一个构造函数,它的prototype将是新创建的实例的原型,但它将使用target的构造函数来创建,如下所示。
let instance = Object.create( newTarget.prototype )
target.apply( instance, argumentsList );
如果没有提供 "newTarget","newTarget "将是 "target",这意味着上述情况将像这样。
let instance = Object.create( target.prototype )
target.apply( instance, argumentsList );
这个方法在内部调用[[_Construct_]]函数的内部方法,带有argumentsList和newTarget,目标是参数[ref]中收到的target。
因此,如果提供了一个非函数或非结构化的target,这个方法调用将抛出一个TypeError异常。同样的,如果newTarget不是一个有原型的函数(_或类),也会抛出一个类型错误。
Reflect.construct( {}, [] )
// TypeError: #<Object> is not a constructor
Reflect.construct( Symbol, [] )
// TypeError: Symbol is not a constructor
Reflect.construct( null, [] )
// TypeError: null is not a constructor
Reflect.construct( Object, [], Symbol )
// Symbol {}
Reflect.construct( Object, [], Reflect )
Reflect.construct( Object, [], Math )
Reflect.construct( Object, [], JSON )
// TypeError: #<Object> is not a constructor
Reflect.enumerate方法在ECMAScript 2016中被移除,它已经过时了(source)。随之,[[Enumerate]]的内部方法也被删除。因此,"Proxy.enumerate "处理方法也被删除(source)。
尽管大多数的Reflect方法和一些operators做的事情几乎一样,但Object的一些静态方法和Function的一些原型方法也如我们所见。然而,"Reflect "诞生的主要目的是为反射带来一个统一的API。
现在所有的事情都可以通过方法调用来完成,比如创建一个实例或者获取、设置和删除对象的属性。随着语言的发展,越来越多的方法将被加入,以简化复杂的事情。
当与 "代理 "结合时,"反射 "是一个强大的工具,我们将在下一课中看到。