📜 ⬆️ ⬇️

We disassemble decorators ES2016



Many of us are probably already tired of this hype around the latest ECMAScript standards. ES6, ES7 ECMAScript Harmony ... It seems that everyone has their own opinion on how to properly name JS. But even despite all this HYIP, what is happening with JavaScript now is the most remarkable thing that has happened to it in the last 5 years at least. The language lives, develops, the community constantly offers new features and syntactic constructions. One of these new designs, certainly worthy of attention, are decorators. Having started searching for materials on this topic, I realized that there is almost nothing about decorators on the Russian-language Internet. Back in July 2015, Addy Osmani presented an excellent article Exploring ES2016 Decorators on Medium. In this regard, I would like to present to your attention the translation of this article into Russian and post it here.

Iterators , generators , list inclusion ... With each innovation, the differences between JavaScript and Python are getting smaller. Today we will talk about one more “pitonopodopnom” proposal for the ECMAScript 2016 standard (aka ES7) - Decorators from Yehuda Katz.

Pattern "Decorator"


Before analyzing the scope and use of decorators for ES2016, let's still find out if there is something similar in other languages? For example in Python. For him, decorators are simply syntactic sugar, which makes it possible to simplify calling higher-order functions . In other words, it is just a function that takes another function, changes its behavior, while not making changes to its source code . We could imagine the simplest decorator as shown below:

@mydecorator def myfunc(): pass 

The expression at the very top of the example ("@mydecorator") is a decorator, the syntax of which will look the same in the ES2016 (ES7) standard, so you have already mastered some of the necessary material.
')
The "@" symbol tells the parser that we are using a decorator, while mydecorator is just the name of some function. Our decorator from the example takes one parameter (namely, the decorated function myfunc ) and returns exactly the same function, but with the modified functionality.

Decorators are very useful if you want to add additional behavior to a function without changing its source code. For example, in order to provide support for memoization, access levels, authentication, instrumentation , logging ... there are really a lot of use cases, the list goes on and on.

Decorators in ES5 and ES2015 (aka ES6)


Implementing a decorator in ES5 using the imperative approach (that is, using pure functions) is a rather trivial task. But due to the fact that ES2015 has native support for classes, we need something more flexible than pure functions to provide the same goals.

ECMAScript’s proposal by Yehuda Katz to add decorators to the next standard provides for annotating and modifying object literals, classes, and their properties during code design, using a declarative rather than an imperative approach.

Let's take a look at some ES2016 decorators with a real example!

ES2016 decorators in action


So, remember what we learned from Python and try to transfer our knowledge to the field of JavaScript. As I said, the syntax for ES7 will be no different, the basic idea, too. Thus, the decorator in ES2016 is some expression that can take a target object, a name, and a descriptor as arguments . Then you simply “apply” it by adding the “@” symbol at the beginning and place it right in front of the part of the code that you are going to decorate. Today decorators can be defined either to define a class or to define a property.

Decorating property


Let's look at the class:

 class Cat { meow() { return `${this.name} says Meow!`} } 

Adding this method to the Cat class prototype gives us something like this:

  Object.defineProperty(Cat.prototype, 'meow', { value: specifiedFunction, enumerable: false, configurable: true, writable: true }); 

Thus, the method allowing the cat to give a vote is hung on the class prototype. At the same time, this method does not participate in the enumeration of the properties of an object, it can also be modified and is available for writing. But imagine that you would like to explicitly ban changes to the implementation of this method. For example, we could do this with the help of the @readonly decorator, which, as the name implies, would give access to this read-only method and prohibit any overrides:

  function readonly(target, key, descriptor) { descriptor.writable = false; return descriptor; } 

And then add the created decorator to the method, as shown below:

  class Cat { @readonly meow() { return `${this.name} says Meow!`} } 

The decorator can also take parameters; in the end, the decorator is just an expression, so both "@readonly" and "@something (parameter)" should work.

Now, before hanging the method on the Cat class prototype, the engine will start the decorator:

  let descriptor = { value: specifiedFunction, enumerable: false, configurable: true, writable: true }; //     ,   "Object.defineProperty", //       ,  // "Object.defineProperty"   descriptor = readonly(Cat.prototype, 'meow', descriptor) || descriptor; Object.defineProperty(Cat.prototype, 'meow', descriptor); 

Thus, the decorated meow method becomes read-only. We can check this behavior:

  let garfield = new Cat(); garfield.meow = () => { console.log('I want lasagne!'); }; // !        . 

Interesting, isn't it? Very soon we will consider decorating classes, but before that let's talk about the support of the decorators from the community. Despite its immaturity, entire libraries of decorators are starting to appear (for example, https://github.com/jayphelps/core-decorators.js from Jay Phelps). Just as we wrote our decorator, you can take exactly the same, but implemented in the library:

  import { readonly } from 'core-decorators'; class Meal { @readonly entree = 'steak'; } let meal = new Meal(); meal.entree = 'salmon'; // ! 

The library is also good because it implements the "@deprecate" decorator, which is quite useful when you change the API, but you need to keep outdated methods for backward compatibility. Here is what the documentation says about this decorator:
The decorator calls console.warn (), and displays a warning message. This message can be overridden. Also supports adding links for further reference.


  import { deprecate } from 'core-decorators'; class Person { @deprecate facepalm() {} @deprecate('We are stopped facepalming.') facepalmHard() {} @deprecate('We are stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) facepalmHarder() {} } let captainPicard = new Person(); captainPicard.facepalm(); // DEPRECATION Person#facepalm will be removed in future versions captainPicard.facepalmHard(); // DEPRECATION Person#facepalmHard: We are stopped facepalming. captainPicard.facepalmHarder(); // DEPRECATION Person#facepalmHarder: We are stopped facepalming. // // See http://knowyourmemes.com/memes/facepalm for more details // 

Decorating class


Next, let's look at decorating classes. According to the proposed specification, the decorator accepts the target constructor. For example, we could create a superhero decorator for the MySuperHero class:

 function superhero(target) { target.isSuperhero = true; target.power = 'flight'; } @superhero class MySuperHero {} console.log(MySuperHero.isSuperhero); // true 

We can go even further and allow our decorator to take parameters, turning it into a factory method:

 function superhero(isSuperhero) { return function (target) { target.isSuperhero = isSuperHero; target.power = 'flight'; } } @superhero(false) class MySuperHero {} console.log(MySuperHero.isSuperhero); // false 

So, decorators in ES2016 can work with classes and property descriptors. And, as we have already found out, the property descriptor and the target object are automatically transferred to them. Having access to a property descriptor, a decorator can do such things as: adding a getter to a property, adding behavior that might look cumbersome without bringing it into the decorator, for example, automatically changing the context of a function call to the current entity when you first try to access the property.

ES2016 decorators and mixins


I thoroughly read the article by Reg Breithwaite Decorators ES2016 as mixins and its previous Functional Mixins, and made a rather interesting use of decorators from there. His proposal is that we introduce some kind of helper, which adds the behavior to the prototype or an object of a particular class. At the same time, a functional mixin that mixes the behavior of an object with a class prototype looks like this:

  function mixin(behaviour, sharedBehaviour = {}) { const instanceKeys = Reflect.ownKeys(behaviour); const sharedKeys = Reflect.ownKeys(sharedBehaviour); const typeTag = Symbol('isa'); function _mixin(clazz) { for (let property of instanceKeys) { Object.defineProperty(clazz.prototype, property, { value: behaviour[property] }); } Object.defineProperty(clazz.prototype, typeTag, { value: true}); return clazz; } for(let property of sharedKeys) { Object.defineProperty(_mixin, property, { value: sharedBehaviour[property], enumerable: sharedBehaviour.propertyIsEnumerable(property) }); } Object.defineProperty(_mixin, Symbol.hasInstance, { value: (i) => !!i[typeTag] }); return _mixin; } 

Fine. Now we can define several mixins and try to decorate a class with them. Let's imagine that we have the “ComicBookCharacter” class:

  class ComicBookCharacter { constructor(first, last) { this.firstName = first; this.lastName = last; } realName() { return this.firstName + ' ' + this.lastName; } } 

Maybe this will be the most boring comic book hero, but we can save the situation by declaring several mixins that add “super power” to our hero and a “multi-purpose Bateman belt” to our hero. To do this, let's use the mixin factory announced above:

  const SuperPowers = mixin({ addPower(name) { this.powers().push(name); return this; }, powers() { return this._powers_pocessed || (this._powers_pocessed = []); } }); const UtilityBelt = mixin({ addToBelt(name) { this.utilities().push(name); return this; }, utilties() { return this._utility_items || (this._utility_items = []); } }); 

Now we can use the @ -syntax with the names of our mixins in order to decorate the “ComicBookCharacter” class declared above, adding to it the desired behavior. Please note we can define several decoration instructions together:

  @SuperPowers @UtilityBelt class ComicBookCharacter { constructor(first, last) { this.firstName = first; this.lastName = last; } realName() { return this.firstName + ' ' + this.lastName; } } 

Now we can create our own Batman:

  const batman = new ComicBookCharacter('Bruce', 'Wayne'); console.log(batman.realName()); // Bruce Wayne batman .addToBelt('batarang') .addToBelt('cape'); console.log(batman.utilities()); // ['batarang', 'cape'] batman .addPower('detective') .addPower('voice sounds like Gollum has asthma'); console.log(batman.powers()); // ['detective', 'voice sounds like Gollum has asthma'] 

As you can see, decorators of this kind are relatively compact and I can easily use them as an alternative for calling functions or as helper for higher order components.

Making Babel understand decorators


Decorators (at the time of writing) are still not approved and are only proposed for addition to the standard. But due to the fact that Babel supports the transplantation of experimental constructions, we can make friends with the decorators.

If you use Babel CLI, you can enable decorators like this:
  babel --optional es7.decorators 

Or you can enable decorator support with the help of a transformer:

  babel.transform('code', { optional: ['es7.decorators'] }); 

Well, in the end, you can play with them in the Babel REPL (for this, enable support for experimental constructions).

Why don't you use decorators in your projects yet?


In the short term, decorators in ES2016 are quite useful for declarative decoration, annotation, type checking, and mixing behavior with classes from ES2015. In the long run, they can serve as a very useful tool for static analysis (which can trigger the creation of tools for type checking during compilation or autocompletion).

They are no different from the decorators of the classical OOP, where an object can be decorated with a certain behavior using a pattern, either statically or dynamically without affecting other objects of the same class. Nevertheless, I consider them a nice addition. The semantics of the decorators for the classes still look damp, but it is worth looking at the Yehuda repository from time to time and checking for updates.

useful links


https://github.com/wycats/javascript-decorators
https://github.com/jayphelps/core-decorators.js">https://github.com/jayphelps/core-decorators.js
http://blog.developsuperpowers.com/eli5-ecmascript-7-decorators/
http://elmasse.imtqy.com/js/decorators-bindings-es7.html
http://raganwald.com/2015/06/26/decorators-in-es7.htmlince>http://raganwald.com/2015/06/26/decorators-in-es7.html

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


All Articles