「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
前言
在之前的文章【译】JavaScript元编程简介 - 掘金 (juejin.cn)中,我们学习到reflection有三种形式:introspection, intercession, modification。在ES6之前的JavaScript版本中,我们仅有一些有限的工具允许我们进行内省和修改程序,比如 Object.keys 和 instanceof 等。
在ES6中,我们得到了Reflect, 它为我们进行元编程提供了很有用的方法。类似于 Math 和 JSON类,Reflect 它不是一个函数也不是一个构造器。它的任务仅仅是提供为反射提供一系列的静态方法。这些方法能够被分为两大类:
- 内省方法(Introspection methods)
- 修改方法(Modification methods)
内省方法是非破坏性方法。它们仅用于内省对象。修改方法是破坏性的,因为它们改变了对象或其行为。这些方法中的大多数来自旧的JavaScript实现,我们也将谈论这一点。
在【译】JavaScript元编程简介 - 掘金 (juejin.cn)中,我们快速的过了一下 Proxy 类,它可以被用于拦截对象的某些操作。Proxy 的 handler 方法和 Reflect 的静态方法具有相同的函数签名。我们将会在 Proxy的文章讨论这为什么它们具有相同的函数签名。为了了解这一点,我们需要看一下ECMAScript的具体标准。
对象的内部方法和内插槽
ECMAScript2015标准中的6.1.7.2章节中讨论了一些奇怪的内部属性和内部方法。这些属性和方法被JavaScript引擎所实现,并且从运行时中被抽取了出来,这也意味着你不能够像访问普通属性一样访问到这些内部方法。
他们通常以[[name]]这样的形式所表示。在继续下面的文章之前,你可以先看一下这篇文章【译】JavaScript中的 "Internal Slots" 和 "Internal Methods" 到底是什么? - 掘金 (juejin.cn)。
每次当你调用 Reflect 的静态方法时,target对象的一些内部方法或者内插槽的属性就会被访问,并且会返回或者改变target队形的行为。Proxy 的handler 会执行相同的内部方法,这意味着 Proxy handler方法和 Reflect的静态方法应该具有相同的函数签名。
内省方法(Introspection Methods)
这些方法纯粹用于内省目的,并且它们不会修改对象其行为或其内部状态。
Reflect.get
Reflect.get(target, propertyKey, [, receiver])
该方法需要接受 target, propertyKey作为函数的入参,并返回 target 对象中键名为 propertyKey 的值。如果具有名称propertyKey的属性是中的getter函数,则receiver参数则被当做 this。如果缺少receiver,则this是target。
调用 reflect.get() 类似于 target[propertykey] 表达式,因为它也搜索 target 原型上的属性值。如果目标上不存在属性,则返回 undefined。
Reflect.has
Reflect.has(target, propertyKey)
Reflect.has方法检查target对象或其原型上的propertyKey属性是否存在。这与in操作符完全相同。如果找到属性,则返回true,否则返回false。这意味着类的实例上的方法名称也会返回true(因为它是继承的属性)。
Reflect.ownKeys
Reflect.ownKeys(target)
Reflect.ownKeys方法返回包含target对象的自己属性的数组。与Object.keys不同,它也包括不可枚举的属性。它还包括Symbol属性。因此,返回值等同于Object.getOwnPropertyNames(target)和Object.getOwnPropertySymbols(target)组合的结果。
Reflect.getOwnPropertyDescriptor
Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.getownPropertyDescriptor 方法返回目标对象的非继承属性的属性描述符。如果target上不存在属性,则返回未定义。
该方法会调用 [[GetOwnProperty]] 内部方法,该内部方法需要使用 target上的 propertyKey,而且target在 [ref]上被接受。因此如果一个非对象的 target (基本数据类型)被传入该方法,那么该方法则会抛出一个错误。
Reflect.getOwnPropertyDescriptor( null, 'prop' )
Reflect.getOwnPropertyDescriptor( 'hello' , 'length' )
// TypeError: Reflect.getOwnPropertyDescriptor called on non-object
我们还有一个类似的Object.getownPropertyDescriptor 方法,返回目标的属性描述符,但在这种情况下,如果目标不是对象,则使用其构造函数(装箱与拆箱)(例如“hey“变为 String('hey'))。而null和未定义的值则会报错(因为它们没有公共的构造函数)
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 方法返回目标对象的原型。如果目标没有原型,则返回null。
Reflect.getPrototypeOf( null)
Reflect.getPrototypeOf( 'hello')
// TypeError: Reflect.getPrototypeOf called on non-object
类似的,我们还有一个相似的方法:Object.getPrototypeOf,但是该方法会进行自动的拆箱装箱,与Object.getownPropertyDescriptor相似,如果传入的值不是一个对象的话,则会自动调用其构造函数。
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 方法用于检查目标对象是否可扩展。如果目标是可扩展的,则表示可以添加新属性,此方法返回true,否则返回false。
Object.preventExtensions()方法可防止您向对象添加任何新属性,但是,允许您对对象执行任何其他操作。Object.freeze()和 Object.seal()方法也可以使对象不可扩展。
我们也有一个类似的方法:Object.isExtensible。OK,我知道你会说,放下Relfect.isExtensible 方法,转而使用 Object.isExtensible 方法。因为它不会抛出错误。但是这有一个问题就是你得为传入的参数进行校验,以确保该函数返回false时的确是因为该对象不可扩展,而不是因为它是一个基础数据类型而返回的false。
修改方法(Modification Methods)
修改方法会修改 target的值和行为或者是其内部状态。
Reflect.deleteProperty
Reflect.deleteProperty(target, propertyKey)
Reflect.deleteProperty 方法从目标对象中删除属性。如果已从目标成功删除属性,则返回true,否则返回false。它相当于 delete 运算符。
类似delete运算符,如果propertyKey不存在,它将始终返回true。此方法不会删除目标原型链上的属性,只能删除自己的属性。此外,无法删除configurable: false的属性。
Reflect.set
Reflect.set(target, propertyKey, value[, receiver])
Reflect.set与 Reflect.get 用检索target属性的值不同,它会使用参数中收到的值更新自己的属性值。如果已存在具有相同名称的propertyKey的属性,则其值将更新否则将创建新属性。
如果属性已经存在并且具有setter 函数,那么this值在 setter 函数内部则为 receiver, 如果没有 receiver ,那么则为this值为target。那方法等价于使用target[propertyKey] = value 表达式。
Reflect.defineProperty
Reflect.defineProperty(target, propertyKey, descriptor)
Reflect.defineProperty 在target对象上创建一个属性名为 propertyKey 的新属性,其中descriptor是属性描述符。
如果属性已存在,则只会更新属性描述符。如果在描述符中仅提供某些属性描述符字段,则将仅更新这些字段。如果已成功设置该属性,则此方法返回true,否则返回false。
我们还有一个类似的方法 Object.defineProperty。但此方法返回更新的目标对象,而不是返回布尔值。此外,如果无法在目标上定义属性,则它会抛出TypeError异常而不是返回false。
Reflect.preventExtensions
Reflect.preventExtensions(target)
Reflect.preventExtensions 方法使目标对象不可扩展。一旦对象不可扩展,就不会将新属性添加到对象中。如果对象未扩展,则此方法返回true,否则返回false。
我们还有一个类似的方法 Object.preventExtensions。但此方法返回更新的目标对象,而不是返回布尔值。如果目标不是对象,则返回原始值。
Object.preventExtensions( null )
// false
Object.preventExtensions( 'hello' )
// 'hello'
Object.preventExtensions( {name: 'ross'} )
// {name: 'ross'}
Reflect.setPrototypeOf
Reflect.setPrototypeOf(target, prototype)
Reflect.setPrototypeOf 方法在目标对象上设置原型原型。如果原型已成功设置在目标上,则返回true,否则返回false。如果目标是不可扩展的,则不会设置原型,并且将返回错误。
var obj = Object.preventExtensions({});
Reflect.setPrototypeOf(obj, null);
// false
我们还有一个类似的方法 Object.setPrototypeOf。但此方法返回更新的目标对象,而不是返回布尔值。如果目标不是对象或null或者目标是不可扩展的,则抛出TypeError。
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 准确的说它不是修改方法。此方法调用一个函数,该函数是具有参数列表的target,并将thisArgument作为函数调用的this值。
这相当于调用target的apply原型方法(如果target是函数),但有一些问题。如果参数列表为null或undefined,则Reflect.apply方法将抛出TypeError,而 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]
Reflect.construct
Reflect.construct(target, argumentsList, [, newTarget])
Reflect.construct 方法从目标构造函数(类)构造一个对象。这相当于使用new运算符与参数列表,如new target(...argumentsList)
newTarget 也是一个构造函数,其原型将是新创建的实例的原型,但它将使用目标的构造函数创建,如下代码所示:
let instance = Object.create( newTarget.prototype )
target.apply( instance, argumentsList );
如果未提供 newTarget,则newTarget 就是 target,这意味着上述代码会变成下面这样:
let instance = Object.create( target.prototype )
target.apply( instance, argumentsList );
总结
虽然大多数Reflect方法与某些操作符、Object的静态方法、Function原型上的方法的作用几乎一样。然而,Reflect背后的主要思想是为了为反射带来统一的API。
现在可以通过方法调用来完成诸如创建实例或获取,设置和删除对象属性。随着语言的发展,将添加越来越多的方法以简化复杂的东西。
Reflect 与 Proxy 相结合时是一个强大的工具,关于 Proxy 的内容我们会在下篇文章中介绍。
原文地址: