📜 ⬆️ ⬇️

About Reflect API available language



Hello! Recently I heard some young front-tenders trying to explain to other young front-tenders what Reflect is in JavaScript. In the end, someone said that this is the same thing as a proxy. The situation reminded me of a joke:

There are two miners:
- Do you understand anything about this?
- Well, I can explain.
- This is understandable, but do you understand anything about this?
')
Here and with Reflect in JS for someone the same situation turned out. It seems to be saying something, but for what it is not clear. In the end, I thought that it was worth telling it again in simple language with examples.

First, let's define what reflection is in programming:
Reflection / Reflect API is an API that provides the ability to conduct reverse engineering of classes, interfaces, functions, methods and modules.

From here it becomes a little clearer what this API should be used for. Reflection API exists in different programming languages ​​and, at times, is used to circumvent the limitations imposed by PL. It is also used to develop various auxiliary utilities and to implement various patterns (such as Injection) and much more.

For example, the Reflection API is in Java. It is used to view information about classes, interfaces, methods, fields, constructors, and annotations during the execution of java programs. For example, using Reflection in Java, you can use the OOP pattern - Public Morozov .

In PHP, there is also the Reflection API, which allows you not only to do reverse engineering, but even allows you to receive doc-blocks of comments, which is used in various systems of auto-documentation.

In JavaScript, Reflect is a built-in object that provides methods for intercepting JavaScript operations. In essence, this is neymspace (like Math). Reflect contains a set of functions that are called exactly the same as methods for Proxy.

Some of these methods are the same as the corresponding methods of the Object or Function class. JavaScript grows and turns into a large and complex PL. Different things come to the language from other languages. At present, Reflect API is not able to as much as in other PL. Nevertheless, there are proposals for expansion, which are not yet included in the standard, but are already in use. For example, Reflection Metadata.

We can say that the Reflect namespace in JS is the result of code refactoring. We have already used the capabilities of the Reflect API, just all of these features were embedded in the base Object class.

Reflect Metadata / Metadata Reflection


This API is designed to get information about objects in runtime. This is a proposal that is not yet a standard. Now actively used polyfil. Today it is actively used in Angular. Injects and decorators (anotator) are implemented using this API.

Actually for the sake of Angular, extended decorators syntax was added to TypeScript. One of the interesting features of decorators is the ability to obtain information about the type of the property or parameter being decorated. For this to work, you need to connect the reflect-metadata library, which extends the standard Reflect object and enable the emitDecoratorMetadata option to the TS config. After that, for properties that have at least one decorator, you can call Reflect.getMetadata with the key "design: type".

What is the difference between Reflect and Proxy?


Reflect is a set of useful methods for working with objects, half of which are rewritten ones that already exist from Object. This was done in order to improve semantics and restore order, since Object is the base class, but it also contains a lot of methods that should not be in it. Also, if you create an object with an empty prototype, then reflection methods disappear from you (I will show by example what this means).

Proxy is a class that always creates a new object with installed handlers to intercept access. It allows you to catch any actions with the object and modify them. Reflect is often used to implement various logic. Below on examples it will be clearly visible.

Use Cases


Well, consider how to use Reflect API. Some examples have long been known, just for this purpose we are used to using methods from the Object class. But, logically, it would be more correct to use them from the Reflect package (packages are terminology from Java).

Autogenerated object fields


We can create an object in which object fields will be created automatically during access to them.

const emptyObj = () => new Proxy({}, { get: (target, key, receiver) => ( Reflect.has(target, key) || Reflect.set(target, key, emptyObj()), Reflect.get(target, key, receiver) ) } ) ; const path = emptyObj(); path.to.virtual.node.in.empty.object = 123; console.log(path.to.virtual.node.in.empty.object); // 123 

Everything is cool, but such an object cannot be serialized into JSON, we get an error. Add a magic serialization method - toJSON

 console.clear(); const emptyObj = () => new Proxy({}, { get: (target, key, receiver) => ( key == 'toJSON' ? () => target : ( Reflect.has(target, key) || Reflect.set(target, key, emptyObj()), Reflect.get(target, key, receiver) ) ) } ) ; const path = emptyObj(); path.to.virtual.node.in.empty.object = 123; console.log(JSON.stringify(path)); // {"to":{"virtual":{"node":{"in":{"empty":{"object":123}}}}}} 

Dynamic constructor call


We have:

 var obj = new F(...args) 

But we want to be able to dynamically call a constructor and create an object. For this there is Reflect.construct:

 var obj = Reflect.construct(F, args) 

It may be necessary for use in factories (OOP gays will understand). Example:

 // Old method function Greeting(name) { this.name = name } Greeting.prototype.greet = function() { return `Hello ${this.name}` } function greetingFactory(name) { var instance = Object.create(Greeting.prototype); Greeting.call(instance, name); return instance; } var obj = greetingFactory('Tuturu'); obj.greet(); 

How do you spell it in 2017:

 class Greeting { constructor(name) { this.name = name } greet() { return `Hello ${this.name}` } } const greetingFactory = name => Reflect.construct(Greeting, [name]); const obj = greetingFactory('Tuturu'); obj.greet(); 

Repeat jQuery behavior


The following line shows how jQuery can be done in 2 lines:

 const $ = document.querySelector.bind(document); Element.prototype.on = Element.prototype.addEventListener; 

It is convenient if you need to quickly build something without dependencies, and writing long native constructs is lazy. But in this implementation there is a minus - throws an exception when working with null:

 console.log( $('some').innerHTML ); error TypeError: Cannot read property 'innerHTML' of null 

Using Proxy and Reflect we can rewrite this example:

 const $ = selector => new Proxy( document.querySelector(selector)||Element, { get: (target, key) => Reflect.get(target, key) } ) ; 

Now, when we try to access the null properties, we simply get undefined:

 console.log( $('some').innerHTML ); // undefined 

So why use Reflect?


Reflect API is more convenient when handling errors. For example, everyone knows the instruction:
Object.defineProperty (obj, name, desc)

In case of failure, an exception will be thrown. But Reflect does not generate exceptions for anything and can return a boolean result:

 try { Object.defineProperty(obj, name, desc); // property defined successfully } catch (e) { // possible failure (and might accidentally catch the wrong exception) } /* --- OR --- */ if (Reflect.defineProperty(obj, name, desc)) { // success } else { // failure } 

This allows you to handle errors through conditions, rather than try-catch. An example of using the Reflect API with error handling:

 try { var foo = Object.freeze({bar: 1}); delete foo.bar; } catch (e) {} 

And now you can write like this:

 var foo = Object.freeze({bar: 1}); if (Reflect.deleteProperty(foo, 'bar')) { console.log('ok'); } else { console.log('error'); } 

But I must say that there are cases when Reflect also throws exceptions.

Some entries are shorter.


Without many words:

 Function.prototype.apply.call(func, obj, args) /* --- OR --- */ Reflect.apply.call(func, obj, args) 

Behavior difference


Example without words:

 Object.getPrototypeOf(1); // undefined Reflect.getPrototypeOf(1); // TypeError 

It seems to be all clear. We conclude that it is better. Reflect API is more logical.

Work with objects with an empty prototype


Given:

 const myObject = Object.create(null); myObject.foo = 123; myObject.hasOwnProperty === undefined; // true //    : Object.prototype.hasOwnProperty.call( myObject, 'foo' ); // true 

As you can see, we no longer have reflection methods, for example, hasOwnProperty. Therefore, we either use the old way, referring to the prototype of the base class, or refer to the Reflect API:

 Reflect.ownKeys(myObject).includes('foo') // true 

findings


Reflect API is the result of refactoring. This namespace contains reflection functions that were previously embedded in the base classes Object, Function ... The behavior and error handling has been changed. In the future, this namespace will be expanded with other reflective tools. Also Reflect API can be considered an integral part when working with Proxy (as can be seen from the examples above).

Source: https://habr.com/ru/post/334546/


All Articles