[JavaScript翻译]介绍 "Reflect "API,用于在JavaScript中进行元编程

257 阅读13分钟

本文由 简悦SimpRead 转码,原文地址 medium.com

在本课中,我们将学习ES2015+中的 "Reflect "全局对象。它......

在这一课中,我们将学习ES2015+中的Reflect全局对象。它提供了各种静态函数来反观和修改JavaScript对象。

image.png (source: unsplash.com)

正如我们在早期课程中所学到的,反思代表了对程序的反省干涉修改。在ES2015(ES6)之前,我们有一些工具可以用来内省和修改程序的行为,如Object.keysinstanceof运算符等。

在ES2015中,我们收到了一个Reflect全局对象,为元编程提供了一些相当有用的方法。像MathJSON对象一样,Reflect不是一个函数,也不能被构造。它的唯一工作是为反射提供静态方法。这些方法可以分为两类。

内省方法是非破坏性的方法。它们只用于反省对象。修改方法是破坏性的,因为它们改变了对象或其行为。这些方法中的大部分都来自于老的JavaScript实现,我们也会讨论这个问题。

早期课程中,我们快速浏览了Proxy类,它被用来拦截对象操作。Proxyhandler方法和Reflect静态方法有相同的函数签名。我们将在Proxy一课中更多地讨论这个问题(即将到来),但让我们简单地讨论一下为什么这些方法的签名是一样的。为此,我们需要看看ECMAScript的规范里面。

对象内部方法和内部槽

ECMAScript 2015规范的6.1.7.2部分谈到了一些奇怪的内部属性和内部方法,对象( Object 的后裔)可以有。这些属性或方法是由JavaScript引擎实现的,但它们被抽象出来,不在运行时使用,因此你不能像普通属性那样在对象上访问它们。

在ECMAScript规范中,这些都是用[[<name>]]符号表示的,其中 "name "是内部属性或内部方法的名称。关于这个话题,我写了一篇简短的文章(下面提到的),请在继续阅读本文之前先看一下。

medium.com/jspoint/wha…

每次你调用Reflect的静态方法,一些内部方法就会被执行,或者访问target对象上的一些内部槽,返回结果或改变target对象的行为。Proxy处理方法执行相同的内部方法,因此Proxy处理方法的方法签名和Reflect的静态方法是相同的。

这些方法纯粹用于自省,它们不会修改对象、其行为或其内部状态。

自省方法

● Reflect.get

Reflect.get(target, propertyKey[, receiver])

当调用Reflect.get方法时,如果有targetpropertyKey,则返回targetpropertyKey属性的值。如果名称为propertyKey的属性是target中的getter函数,receiver参数将作为this使用。如果缺少receiverthis就是target

Reflect.get()调用类似于target[propertyKey]表达式,因为它也是在target的原型上搜索属性值。如果一个属性在target上不存在,就会返回undefined

image.png

(reflect/get.js)

💡 你也可以在数组上使用_Reflect.get_(当_target_是一个_Array_),使用_propertyKey_作为_number__string_的索引。

这个方法在内部调用对象的[[Get]]内部方法,使用propertyKeyreceiver在目标上,该目标是参数[ref]中收到的target。因此,如果提供了一个非对象的targetprimitive 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因为它是一个继承的属性)。

image.png

(reflect/has.js)

💡 你也可以在数组上使用_Reflect.has_(当_target_是一个_Array_),使用_propertyKey_作为_number__string_索引。

这个方法在内部调用对象的[[HasProperty]]内部方法,目标是参数[ref]中收到的targetpropertyKey。因此,如果提供了一个非对象的targetprimitive 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)的组合结果。

image.png

(reflect/ownKeys.js)

这个方法在内部调用对象的[[OwnPropertyKeys]]内部方法,这个对象是在参数[ref]中收到的target。因此,如果提供了一个非对象的targetprimitive value),这个方法的调用将抛出一个TypeError异常。

Reflect.ownKeys( null )
Reflect.ownKeys( 'hello' )
// TypeError: Reflect.ownKeys called on non-object

● Reflect.getOwnPropertyDescriptor

Reflect.getOwnPropertyDescriptor(target, propertyKey)

Reflect.getOwnPropertyDescriptor方法返回target对象的非继承属性propertyKeyproperty descriptor 。如果该属性在target上不存在,将返回undefined

image.png

(reflect/getOwnPropertyDescriptor.js)

💡 你也可以在数组上使用_Reflect.getOwnPropertyDescriptor_(当_target_是一个_Array_),使用_propertyKey_作为_number__string_索引。

这个方法在内部调用[[GetOwnProperty]]对象的内部方法,目标是参数[ref]中收到的target,具有propertyKey。因此,如果提供了一个非对象的targetprimitive value),这个方法调用将抛出一个TypeError异常。

Reflect.getOwnPropertyDescriptor( null, 'prop' )
Reflect.getOwnPropertyDescriptor( 'hello', 'length' )
// TypeError: Reflect.getOwnPropertyDescriptor called on non-object

我们也有一个类似的Object.getOwnPropertyDescriptor方法,返回目标的属性描述符,但是在这种情况下,如果目标不是一个对象。它将被强制为一个对象(在ES2015中),使用它的构造函数(如 "hey"变成String("hey") ),但nullundefined值除外(因为它们没有公共构造函数)。

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

image.png

(reflect/getPrototypeOf.js)

这个方法在内部调用对象的[[GetPrototypeOf]]内部方法,这个对象是在参数[ref]中收到的target。因此,如果提供了一个非对象的targetprimitive value),这个方法的调用将抛出一个TypeError异常。

Reflect.getPrototypeOf( null )
Reflect.getPrototypeOf( 'hello' )
// TypeError: Reflect.getPrototypeOf called on non-object

我们也有一个类似的Object.getPrototypeOf方法,返回目标的原型,但在这种情况下,如果目标不是一个对象,它将被强制为一个对象(在ES2015中),使用其构造函数,除了nullundefined值(因为它们没有公共构造函数)。

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

image.png

(reflect/isExtensible.js)

Object.preventExtenstions()方法阻止你向对象添加任何新的属性,但是,你可以对对象进行任何其他操作。Object.freeze()和Object.seal()方法也使对象不可扩展。

这个方法在内部调用对象的[[IsExtensible]]内部方法,这个目标是参数[ref]中收到的目标。因此,如果提供了一个非对象的targetprimitive 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的原型链上的属性,只有自己的属性可以被删除。另外,不可配置的属性不能被删除。

image.png

(reflect/deleteProperty.js)

💡 你也可以在数组上使用_Reflect.deleteProperty_(当_target_是一个_Array_时),使用_propertyKey_作为_number__string_索引。

这个方法在内部调用对象的[[Delete]]内部方法,目标上的propertyKey是参数[ref]中收到的target。因此,如果提供了一个非对象的targetprimitive 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类似,但它不是检索targetpropertyKey属性的值,而是用参数中收到的value更新target自己的属性值。如果一个名称为propertyKey的属性已经存在,其值将被更新,否则将创建一个新的属性。

如果属性已经存在并且有一个setter函数,如果提供的话,setter函数里面的this值将是receiver,否则将使用target。这个方法等同于使用target[_propertyKey_] = value表达式。

image.png

(reflect/set.js)

💡 你也可以在数组上使用Reflect.set(当target是一个Array时),使用propertyKey作为numberstring索引。

这个方法在内部调用对象的[[Set]]内部方法,其中有propertyKey, valuereceiver,目标是参数[ref]中收到的target。因此,如果提供了一个非对象的targetprimitive 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.definePropertytarget对象上创建一个新的属性,名称为propertyKeydescriptor是属性描述符。

如果该属性已经存在,只有属性描述符会被更新。如果在descriptor中只提供了一些属性描述符字段,那么只有这些字段将被更新。如果属性被成功设置,该方法返回true,否则返回false

image.png

(reflect/defineProperty.js)

💡 你也可以在数组上使用_Reflect.defineProperty_(当_target_是一个_Array_),使用_propertyKey_作为_number__string_索引。

这个方法在内部调用对象的[[DefineOwnProperty]]内部方法,并在目标上使用propertyKeydescriptor,该目标是参数[ref]中收到的target。因此,如果提供了一个非对象的targetprimitive 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

image.png

(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不是一个有效的Objectnull,那么就会抛出一个TypeError

Reflect.setPrototypeOf({}, "")\
Reflect.setPrototypeOf(function(){}, "")\
*// TypeError: Object prototype may only be an Object or null*

image.png

(reflect/setPrototypeOf.js)

这个方法在内部调用[[SetPrototypeOf]]对象的内部方法,目标是参数[ref]中收到的target,具有prototype。因此,如果提供了一个非对象的targetprimitive 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]]内部方法,并在目标上使用thisArgumentargumentsList,该目标是参数[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 );

image.png

(reflect/construct.js)

这个方法在内部调用[[_Construct_]]函数的内部方法,带有argumentsListnewTarget,目标是参数[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。

现在所有的事情都可以通过方法调用来完成,比如创建一个实例或者获取、设置和删除对象的属性。随着语言的发展,越来越多的方法将被加入,以简化复杂的事情。

当与 "代理 "结合时,"反射 "是一个强大的工具,我们将在下一课中看到。


www.deepl.com 翻译