JavaScript 反射和 Reflect API





1.00/5 (2投票s)
介绍 JavaScript Reflect API 为开发者提供的各种方法。
引言
在计算机编程的语境下,反射(Reflection)被定义为在运行时检查、自省和修改自身结构和行为的能力(该定义来自 Wikipedia 页面)。此外,它通常被称为元编程。因此,您可以在运行时操作对象的变量、属性和方法。使用 JavaScript 语言,可以进行反射。然而,在过去,它的能力有限,并且反射方法对许多开发者来说并不直接。但如今,情况已不再如此,Reflect
对象提供了更具意义的方法,帮助开发者轻松进行反射编程。因此,我们将探讨并了解 Reflect
对象能为我们提供什么,特别是它的 static
方法。好的,我们开始吧。
目录
- 什么是 Reflect API?
- Reflect 对象提供了哪些方法?
- Reflect.apply(target, receiver, args)
- Reflect.construct(target, args, prototype)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target,name)
- Reflect.set(target,name,value)
- Reflect.get(target,name,receiver)
- Reflect.getOwnPropertyDescriptor(target,name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, newProto)
- Reflect.has(target,name)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.ownKeys(target)
- 摘要
- 历史
什么是 Reflect API?
关于 Reflect
API,需要记住以下几点。
-
它使用
Reflect
,这是一个全局的 & 静态对象,因此您无法创建它的实例。同样,它的所有方法都是static
的。 -
它提供了运行时级别的检查和操作对象属性的功能,也称为元编程。此外,在 ES6 之前,JavaScript 语言确实提供了对象反射 API,但这些 API 并不真正有序,并且在失败时会抛出异常。因此,今天,
Reflect
API 在Reflect
对象帮助下,改进了我们进行元/反射编程的方式。
Reflect 对象提供了哪些方法?
Reflect.apply(target, receiver, args)
此方法调用目标函数并使用指定的参数进行调用。换句话说,如果您想调用某个函数,但又不直接调用它,而是通过此方法调用目标函数。
此方法接受三个参数
target
- 第一个参数,代表目标函数receiver
- 第二个参数,代表目标函数中this
的值args
- 第三个参数,代表目标函数的参数,以数组形式给出
请看下面的示例
/** start of Reflect.apply() */
//let's define a function
function getSum(num1, num2) {
return `${this.value}${num1 + num2}`;
}
//let's try to invoke the function using Reflect.apply()
const returnedValueOfFunc = Reflect.apply(getSum, { value:'Sum of 1 and 2 is '}, [ 1, 2]);
console.log(returnedValueOfFunc); //output: Sum of 1 and 2 is 3
/** end of Reflect.apply() */
Reflect.construct(target,args,prototype)
此方法用于将函数作为构造函数调用。换句话说,此方法返回由目标构造函数创建的新实例。
此方法接受三个参数
target
- 第一个参数,代表目标构造函数args
- 第二个参数,代表目标构造函数的参数。此参数是可选的。prototype
- 第三个参数,代表另一个构造函数,其原型将用作目标构造函数原型的原型。此参数是可选的。
请看下面的示例
/** start of Reflect.constructor */
//let's define a constructor function
function Customer(title,firstName, lastName) {
this.title = title;
this.firstName = firstName;
this.lastName = lastName;
this.showFullName = function () {
return `${this.title}. ${lastName}, ${firstName} is from the ${this.country}`;
}
}
//let's define another constructor set the prototype to add a new property.
function Employee() { }
Employee.prototype.country = 'Philippines';
const myCustomer = Reflect.construct(Customer, ['Dr','Jin Vincent', 'Necesario'],Employee);
console.log(myCustomer.showFullName()); //output: Dr. Necesario,
//Jin Vincent is from the Philippines
/** end of Reflect.constructor */
Reflect.defineProperty(target, name, desc)
您可能已经猜到,此方法用于在对象上定义新属性或更新现有属性。如果您是这么想的,那您就猜对了。
此方法接受三个参数
target
- 第一个参数是要用来定义或修改属性的对象name
- 第二个参数是要定义或修改的属性的名称desc
- 第三个参数是定义或修改的属性的描述符
此外,它还有一个等效的方法是 Object.defineProperty()
。既然我们知道了这一点,您可能会想:“有什么区别?” 我将在下一节回答这个问题。之后,我们将看代码示例。
Reflect.defineProperty() 和 Object.defineProperty() 有什么区别?
基本上,这些方法做的事情是相同的,但主要区别在于这些方法返回的值。区别在于 Reflect.defineProperty()
方法返回一个 Boolean
,而 Object.defineProperty()
返回修改后的对象。此外,如果 Object.defineProperty()
方法失败,它会抛出异常,而 Reflect.defineProperty()
方法则返回 false
。
什么是数据属性和访问器属性?
在看代码示例之前,让我们先尝试理解 Reflect.defineProperty()
方法的第三个参数。在任何面向对象的语言中,每个对象的属性要么是数据属性,要么是访问器属性。
基本上,数据属性有一个值,该值可能是可读或不可读,也可能是可写或不可写,而访问器属性具有一对 getter-setter 函数来设置和检索属性值。
使用 Reflect.define() 和定义数据属性描述符
在我们深入代码之前,让我们先看看描述符对象属性
value
- 这是与属性关联的值。默认情况下为undefined
。writable
- 如果设置为true
,则可以使用赋值运算符更改属性值。默认情况下为false
。configurable
- 如果设置为true
,则可以更改属性的特性。默认情况下为false
。enumerable
- 如果设置为true
,则该属性会出现在for ...in
循环和Object.keys()
方法中。默认情况下为false
。
让我们看一个将属性定义为可写、可配置和可枚举的代码示例。
/** start of Reflect.defineProperty */
const book = {};
//let's define a property that is writable, configurable and enumerable
Reflect.defineProperty(book, "title", {
value: "JavaScript For Kids",
writable: true,
configurable: true,
enumerable:true
});
//let's check the book object
console.log(book); //output: {title: "JavaScript For Kids"}
//let's check the title of the book
console.log(book.title); //output: JavaScript For Kids
//let's change the value of the Book property,
//this is possible because writable is set to true
book.title = "Beginning Node.js";
//let's check the title of the book
console.log(book.title); //output: Beginning Node.js
//let's check if we can enumerate the title property
for (const key in book) {
console.log(key); //output: title
}
/** end of Reflect.defineProperty */
另一个示例,将属性定义为不可写、不可配置且不可枚举。
/** start of Reflect.defineProperty */
const laptop = {};
//let's define a property that isn't writable, configurable and enumerable
Reflect.defineProperty(laptop, "brand", {
value: "IBM",
writable: false,
configurable: false,
enumerable: false
});
//let's check the laptop object
console.log(laptop); //output: {brand: "IBM"}
//let's check the brand of the laptop
console.log(laptop.brand); //output: IBM
//let's change the value of the brand property,
//this is not possible because writable is set to false
laptop.brand = "DELL";
//let's check the brand of the laptop
console.log(laptop.brand); //output: IBM
//let's check if we can enumerate the brand property
for (const key in laptop) {
console.log(key); //output: n/a
}
/** end of Reflect.defineProperty */
使用 Reflect.define() 和定义访问器属性描述符
同样,在深入代码示例之前,让我们看看访问器属性描述符属性
get
- 返回属性值的函数set
- 设置属性值的函数configurable
- 如果设置为true
,则可以更改属性描述符。默认情况下为false
。enumerable
- 如果设置为true
,则该属性会出现在for..in
循环和Object.keys()
方法中。默认情况下为false
。
让我们看下面的代码示例
/** start of accessor property */
/** start of Reflect.defineProperty */
const laundryShop = {
__defaultName__: "Queens Care Laundry Shop"
}
Reflect.defineProperty(laundryShop, "businessName", {
get: function () {
return this.__defaultName__;
},
set: function (value){
this.__defaultName__ = value;
},
configurable: true,
enumerable: true
});
console.log(laundryShop); //output: {__defaultName__: "Queens Care Laundry Shop"}
console.log(laundryShop.businessName); //output: Queens Care Laundry Shop
laundryShop.businessName = "Laundry Shop";
console.log(laundryShop.businessName); //output: Laundry Shop
/** end of accessor property */
/** end of Reflect.defineProperty */
Reflect.deleteProperty(target,name)
方法的名称本身就描述了它的作用。它基本上会删除对象的属性。
此方法接受两个参数
target
- 第一个参数是目标/引用对象name
- 第二个参数是要删除的属性的名称
让我们看下面的代码示例
// /** start of Reflect.deleteProperty */
let car = {
model: "Toyota Hilux",
yearModel: 2020
};
//let us see the object before removing the model property.
console.log(car); //output: {model: "Toyota Hilux", yearModel: 2020}
Reflect.deleteProperty(car, "model");
//let use the object after the removal of the model property.
console.log(car); //output: { yearModel: 2020 }
/** end of Reflect.deleteProperty */
Reflect.set(target, name, value)
此方法用于设置对象属性的值。
此方法接受三个参数
target
- 第一个参数是目标/引用对象name
- 第二个参数是对象属性的名称value
- 第三个参数是属性的值
让我们看下面的代码示例
/** Start of Reflect.set */
const computer2 = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
console.log(computer2);
//output: {processor: "Intel", brand: "Dell", operatingSystem: "windows 7"}
Reflect.set(computer2, "processor", "AMD");
console.log(computer2);
//output: {processor: "AMD", brand: "Dell", operatingSystem: "windows 7"}
// /** end of Reflect.set */
Reflect.get(target, name, receiver)
显然,此方法与 Reflect.set()
完全相反。此方法用于检索对象属性的值。
此方法接受三个参数
target
- 第一个参数是目标/引用对象name
- 第二个参数是对象属性名receiver
- 第三个参数是接收者
让我们看下面的代码示例
/** Start of Reflect.get */
var computer1 = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
console.log(computer1);
Reflect.get(computer1, "processor");
console.log(computer1.processor);
/** end of Reflect.get */
注意:如果属性是访问器属性,则我们可以提供可选的第三个参数,该参数将是在 get
函数中 this
的值。
让我们看下面的代码示例
/** start of Reflect.get with 3rd argument */
const dinoComputer = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
Reflect.defineProperty(dinoComputer, "computerDetails", {
get: function() {
return new String().concat(`*********Computer Details********\r\n`,
`****Processor: ${this.processor}***********\r\n`,
`****Brand: ${this.brand}*****************\r\n`,
`****Operating System: ${this.operatingSystem}*\r\n`);
}
});
console.log(dinoComputer);
let oldComputer = Reflect.get(dinoComputer, "computerDetails",
{ processor: "AMD K62",
brand: "Clone",
operatingSystem: "Windows XP" });
console.log(oldComputer);
/** end of Reflect.get with 3rd argument */
输出
Reflect.getOwnPropertyDescriptor(target,name)
此方法用于检索对象属性的描述符。实现起来相当容易。
此方法接受两个参数
target
- 第一个参数是目标/引用对象name
- 第二个参数是对象属性的名称
让我们看下面的代码示例
/** start of Reflect.getOwnPropertyDescriptor */
const myDog = {
yourPet: true,
name: "Bruno"
}
const descriptor = Reflect.getOwnPropertyDescriptor(myDog, "name");
console.log(descriptor.value); //output: Bruno
console.log(descriptor.writable); //output: true
console.log(descriptor.enumerable);//output: true
console.log(descriptor.configurable); //output: true
/** end of Reflect.getOwnPropertyDescriptor */
Reflect.getPrototypeOf(target)
此方法用于检索对象的内部原型,即对象的内部属性的值。此方法只有一个参数,即目标/引用对象。
需要注意的一点是,它与 Object.getPrototypeOf()
方法相同。
让我们看下面的代码示例
/** start of Reflect.getPrototypeOf*/
const product = {
__proto__: {
category: {
id: "1",
name: "Electronics",
description: "Electronic devices"
}
}
}
const myCategoryResult = Reflect.getPrototypeOf(product);
console.log(myCategoryResult.category);
//output: { id: "1", name: "Electronics", description: "Electronic devices" }
/** end of Reflect.getPrototypeOf */
Reflect.setPrototypeOf(target, newProto)
此方法用于设置对象的内部原型(__proto__
)属性的值。
此方法接受两个参数
target
- 第一个参数是目标/引用对象newProto
- 第二个参数是对象的__proto__
属性值
让我们看下面的代码示例
/**start of Reflect.setPrototypeOf */
const anime = {
popularAnimeShow: "Voltes V"
}
Reflect.setPrototypeOf(anime, {
fullName: "Super Electromagnetic Machine Voltes V"
});
console.log(anime.__proto__.fullName);
//output: Super Electromagnetic Machine Voltes V
/** end of Reflect.setPrototypeOf */
Reflect.has(target,name)
此方法用于检查属性是否存在于对象中。如果属性存在,则返回 true
,否则返回 false
。
此方法接受两个参数
target
- 第一个参数是目标/引用对象name
- 第二个参数是对象属性的名称
让我们看下面的代码示例
/** start of Reflect.has */
const band = {
name: "EHeads",
songs: ['Ang Huling El Bimbo', 'With A Smile']
}
console.log(Reflect.has(band, "name")); //output: true
console.log(Reflect.has(band, "songs")); //output: true
console.log(Reflect.has(band, "producer")); //output: false
/** end of Reflect.has */
Reflect.isExtensible(target)
此方法用于检查对象是否可扩展。换句话说,我们是否可以向对象添加新属性。此方法只有一个参数,即目标/引用对象。
让我们看下面的代码示例
/** start of Reflect.isExtensible */
const problem = {
problemIs: "I'm in love with a college girl"
}
console.log(Reflect.isExtensible(problem)); //output: true
Reflect.preventExtensions(problem); //let's prevent this object to be extended.
//This is the same as Object.preventExtensions(problem);
console.log(Reflect.isExtensible(problem)); //output: false
/** end of Reflect.isExtensible */
/** start of Reflect.preventExtensions */
要知道的一个好消息是,我们也可以通过以下方法将对象标记为不可扩展:
Object.preventExtension()
Object.freeze()
Object.seal()
Reflect.preventExtensions(target)
此方法用于将对象标记为不可扩展。它返回一个 Boolean
,指示是否成功。此方法只有一个参数,即目标/引用对象。
让我们看下面的代码示例
/** start of Reflect.preventExtensions */
const song = {
title: "Magasin",
band: "EHeads"
}
console.log(Reflect.isExtensible(song)); //output: true
Reflect.preventExtensions(song);
//This is the same as Object.preventExtensions(song);
console.log(Reflect.isExtensible(song)); //output: false
/** end of Reflect.preventExtensions */
Reflect.ownKeys(target)
此方法返回一个对象键属性的数组。但是,它会忽略继承的属性(__proto__
)。此方法只有一个参数,即目标/引用对象。
让我们看下面的代码示例
/** start of Reflect.ownKeys */
const currency = {
name: "USD",
symbol: "$",
globalCurrency: true,
__proto__: {
country: "USA"
}
}
const keys = Reflect.ownKeys(currency);
console.log(keys); //output: ["name", "symbol", "globalCurrency"]
console.log(keys.length);//output: 3
/** Output:
* name
symbol
globalCurrency
*/
keys.forEach(element => {
console.log(element);
});
/** end of Reflect.ownKeys */
摘要
在本篇文章中,我们通过 Reflect
对象学习了 JavaScript 反射 API。不仅如此,我们还探讨了 Reflect
对象提供的大部分方法,并看到了如何在特定场景下以不同的方式实现它们。总而言之,本文介绍了 JavaScript Reflect
API。
希望您喜欢这篇文章,就像我喜欢写它一样。请继续关注更多内容。下次再见,祝您编程愉快!
历史
- 2020 年 8 月 8 日:初始版本