An article makes you understand Reflect and Reflect metadata(JavaScript)
introduction
We often see the use of Reflect in Javascript, but we always don't understand the difference between it and Object. In addition, we don't often use it in work, so we are in a state of ignorance. So make up your mind to use this article to understand it thoroughly
This paper mainly covers the following four sections:
- Reflect
- What is reflect APIs (the difference between reflect and Object)
- Simple example: Object vs Reflect
- Reflect simple summary
- Reflect Metadata(npm package)
- Appearance background
- How to install
- Usage of Reflect Metadata
- epilogue
- reference
Reflect
What is Reflect APIs
Reflect is a built-in object used to provide methods to intercept JavaScript operations. All properties and methods of reflect are static.
This explanation is quite abstract. In human words, Reflect provides a series of static methods to operate JS objects
Before ES6, Javascript did not have a unified namespace to manage operations on other objects. For example, we may use object Keys (car) gets all the attributes of the car object
However, we will use the form of property in car to judge whether a property exists in car I'm sure you can see that this leads to code fragmentation. Why can't we have a new series of APIs to support all these operations on objects? So at this time, Javascript launched Reflect. You can use Reflect Has (car, property) can be used to judge whether a property exists in car. At the same time, Reflect Ownkeys (car) to get all the attributes of car And all are done by calling the static function in Reflect. Is it much more convenient and unified?
The following are the 13 static functions under Reflect and their corresponding JS implementations:
- Reflect.apply(target, thisArgument, argumentsList)
- Similar to function prototype. apply()
- Reflect.construct(target, argumentsList[, newTarget])
- Similar to new target(...argumentsList)
- Reflect.defineProperty(target, propertyKey, attributes)
- Similar to object defineProperty()
- Reflect.deleteProperty(target, propertyKey)
- Similar to delete target[propertyKey]
- Reflect.get(target, propertyKey[, receiver])
- Similar to target[propertyKey]
- Reflect.getOwnPropertyDescriptor(target, propertyKey)
- Similar to object getOwnPropertyDescriptor()
- Reflect.getPrototypeOf(target)
- Similar to object getPrototypeOf()
- Reflect.has(target, propertyKey)
- Similar to property in target
- Reflect.isExtensible(target)
- Similar to object isExtensible(target)
- Reflect.ownKeys(target)
- Similar to object keys(target)
- Reflect.preventExtensions(target)
- Similar to object preventExtensions(target)
- Reflect.set(target, propertyKey, value[, receiver])
- Similar to target[property] = value
- Reflect.setPrototypeOf(target, prototype)
- Similar to object setPrototypeOf(target, prototype)
Through the above functions, you can find that all functions in Reflect can find corresponding implementations (objects, functions) in JS at present. Now you don't need to remember those complex grammars. All functions can be done with Reflect. JS officials also suggest that developer s can start using Reflect instead. In the future, new features on Object operation will be added to Reflect instead of Object. However, for backward compatibility, the existing functions in the Object will not be removed
Extended reading: Comparing Reflect and Object methods
Simple example: Object vs Reflect
If we define a new property for the car Object, we need to use try in the Object Catch to catch errors
And reflect Defineproperty() will directly return true or false to indicate success
// Object implementation try { Object.defineProperty(car, name, desc); // property defined successfully } catch (e) { // possible failure (and might accidentally catch the wrong exception) } // Reflect implementation if (Reflect.defineProperty(car, name, desc)) { // success } else { // failure }
Reflect simple summary
Reflect provides a series of static functions to operate other Javascript objects. Its advantages are:
- Functionalize all object operations
- Richer return values
- More uniform naming
- More reliable support for function apply - reflect apply(f, obj, args)
-
const car = () => {} car.apply = (v) => {console.log('car.apply() runs with', v)} car.apply(void 0, [1]); // car's own apply() will override function prototype. apply Reflect.apply(car.apply, car, [1]) // Reflect can avoid this problem
-
- Better control of this binding
- Reflect.set()
- Reflect.get()
- It corresponds to the operation in Proxy one by one
Extended reading:
Reflect Metadata
Reflect metadata is mainly used to add / read the metadata of an object when the code is declared
Appearance background
Reflect Metadata is a widely used third-party npm package. At the same time, Ron Buckton, the author of the package, is also the core contributor of Typescript. He submitted a proposal to include metadata in the official Typescript (ES7) in 2015, but so far, he has not completed much other work, so he has not put it on the agenda. Interested friends can go to the following links to view specific information:
- Metadata Proposal - ECMAScript: https://rbuckton.github.io/reflect-metadata/
- Ron Buckton's response in github: https://github.com/rbuckton/reflect-metadata/issues/9
How to install
In TypeScript, you only need to:
- npm i reflect-metadata --save
- In tsconfig Configure the emitDecoratorMetadata option in JSON
- Import 'reflect metadata' in the used file or in index d. TS global import
Usage of Reflect Metadata
The API of Reflect Metadata can be used to add metadata to objects and their attributes:
const car = { brand: 'BMW', model: 'X6 2012', price: 99999, getMaxSpeed() { console.log(`Max speed is 200km/h`); } } // Add a metadata called 'desc' to the car object Reflect.defineMetadata('desc', 'This is a good car', car); // Add a metadata called 'desc' to the price of the car (the price here can be other values or attributes that do not exist in the car) Reflect.defineMetadata('desc', 'This is so cheap', car, 'price'); // Add a metadata called 'desc' to the model of car (the model here can be other values or attributes where car does not exist) Reflect.defineMetadata('note', 'This model is too old', car, 'model'); // Check if metadata exists console.log(Reflect.hasMetadata('desc', car)); // Output: true console.log(Reflect.hasMetadata('desc', car, 'price')); // Output: true console.log(Reflect.hasMetadata('note', car, 'model')); // Output: true console.log(Reflect.hasMetadata('desc', car, 'brand')); // Output: false // Acquiring Metadata console.log(Reflect.getMetadata('desc', car)); // Output: 'This is a good car' console.log(Reflect.getMetadata('desc', car, 'price')); // Output: 'this is so heap' console.log(Reflect.getMetadata('note', car, 'model')); // Output: 'This model is too old' console.log(Reflect.getMetadata('desc', car, 'brand')); // Output: undefined // Get the keys of all metadata on the object console.log(Reflect.getMetadataKeys(car)) // Output: ['desc '] console.log(Reflect.getMetadataKeys(car, 'price')) // Output: ['desc '] console.log(Reflect.getMetadataKeys(car, 'model')) // Output: ['note '] console.log(Reflect.getMetadataKeys(car, 'brand')) // Output: []
It can also be used as a decorator to act on classes or class attributes.
Here we write a decorator that can count the number of methods and properties of Class without new instance:
// Define all metadata to this global variable const globalMeta = Object.create(null); const propertyCollector = (target: Object, propertyKey: string | symbol, descriptor?: any) => { // Put the names of properties into an array called properties const properties = Reflect.getMetadata('properties', globalMeta); if (properties) { Reflect.defineMetadata('properties', [...properties, propertyKey], globalMeta); } else { Reflect.defineMetadata('properties', [propertyKey], globalMeta); } } const methodCollector = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { // Put the names of methods into an array called methods const methodss = Reflect.getMetadata('methods', globalMeta); if (methodss) { Reflect.defineMetadata('methods', [...methodss, propertyKey], globalMeta); } else { Reflect.defineMetadata('methods', [propertyKey], globalMeta); } } const classCollector = (constructor: Function) => { // Save the name of class to the className of globalMeta Reflect.defineMetadata('className', constructor.name, globalMeta); } @classCollector class Car { @propertyCollector private speed = 0; @propertyCollector private brand = 'BMW'; @propertyCollector private model = 'X6 2012'; @methodCollector run() { this.speed = 100; console.log(`The car is running at 100km/h`) } @methodCollector stop() { this.speed = 0; console.log(`The car stopped`) } } const className = Reflect.getMetadata('className', globalMeta); const properties = Reflect.getMetadata('properties', globalMeta); const methods = Reflect.getMetadata('methods', globalMeta); console.log( `Class [${className}] has ${properties.length} properties: ${properties.join(', ')}\t Class [${className}] has ${methods.length} methods: ${methods.join(', ')}`, ); // output // Class [Car] has 3 properties: speed, brand, model // Class [Car] has 2 methods: run, stop
epilogue
I hope that through this article, you can have a new understanding of Reflect and will write the wonderful usage of linking Reflect and Proxy in the future (opening a new chapter of meta programming in JS)
reference
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
- https://blog.greenroots.info/javascript-why-reflect-apis
- https://medium.com/jspoint/introduction-to-reflect-metadata-package-and-its-ecmascript-proposal-8798405d7d88