An article makes you understand Reflect and Reflect metadata(JavaScript)

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:

  1. Functionalize all object operations
  2. Richer return values
  3. More uniform naming
  4. 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
      
  5. Better control of this binding
    • Reflect.set()
    • Reflect.get()
  6. 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:

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

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  2. https://blog.greenroots.info/javascript-why-reflect-apis
  3. https://medium.com/jspoint/introduction-to-reflect-metadata-package-and-its-ecmascript-proposal-8798405d7d88

Keywords: Javascript Front-end TypeScript

Added by ilikephp on Sun, 06 Feb 2022 07:58:13 +0200