Our Global Presence
Canada
57 Sherway St,
Stoney Creek, ON
L8J 0J3
India
606, Suvas Scala,
S P Ring Road, Nikol,
Ahmedabad 380049
USA
1131 Baycrest Drive,
Wesley Chapel,
FL 33544
When you write a class in JavaScript, you might have had to add more features to the methods in a class. But sometimes they look quite nasty and messy.
How can you make the process more elegant? In this post, we will talk about a promising feature, a decorator.
This feature isn’t included in the newest ECMA-262, JavaScript in other words. You should always use Babel to use this in your project.
The examples I’ve attached to this post were written in JSFiddle, with the Babel + JSX configuration. If you want to use this feature in your project, you ought to set up Babel on your own.
class Medium { constructor(writer) { this.writer = writer; } getWriter() { return this.writer; } }
There’s a class, Medium
, that takes the name of the writer in its constructor. And there’s a function that returns the writer’s name.
Let’s create a property that is of Medium
type.
const medium = new Medium('Jane'); const fakeMedium = { writer: 'Fake Jane', getWriter: medium.getWriter, };
medium
is created using Medium
‘s constructor function, unlike fakeMedium
which is an object literal. But it has the same properties as medium
.
Now, let’s compare the result of getWriter
from each.
medium.getWriter(); // Jane fakeMedium.getWriter(); // Fake Jane
Why are the values different?
It’s because JavaScript’s normal function this
is bound to the object that actually invokes the function.
medium.getWriter()
is called by the medium
object, however, fakeMedium.getWriter()
is called by fakeMedium. So, the this
inside the function, getWriter, looks up the value from fakeMedium
.
This article outlines the difference between normal functions and arrow functions.
To get the same result as when medium.getWriter
is called, let’s use Object.defineProperty
. What Object.defineProperty
does is define new properties on the object or modify the existing properties on the object and then it returns the object.
const fakeMedium = { ... }; let isDefining; let fn = fakeMedium.getWriter; Object.defineProperty(fakeMedium, 'getWriter', { get() { console.log('Access to getWriter'); if (isDefining) { return fn; } isDefining = true; const boundFn = this.getWriter.bind(medium); isDefining = false; return boundFn; } });
Whenever fakeMedium.getWriter
is called, Access to getWriter
will be printed twice. But why twice?
fakeMedium.getWriter()
, its getter-mode is detected and runs the customized get
method.get
method, the getWriter
is newly bound by medium
– this.getWriter.bind(medium)
. Here, this
refers to fakeMedium
itself. So it’s the same as fakeMedium.getWriter.bind(medium)
. That’s why its get
is called once again.isDefining
is set to true, so the codes under the if-condition won’t be executed until isDefining
is set back to false again.But this way is really a pain in the neck. Because every time you make a new instance of Medium
, you should do this again.
Can’t we do this in a more elegant way?
Any function can be a decorator. Basically, you can use a decorator for either a class or a method in a class. It takes three arguments – target, value, and descriptor.
function decorator(target, value, descriptor) {}
target
refers to either the class or a prototype of the class.value
is undefined
for a class and is the name of the method for a method.descriptor
is an object that contains definable properties on an object – such as configurable, writable, enumerable, and value. It’s undefined
for a class.function autobind(target, value, descriptor) {} class Medium { ... @autobind getWriter() { return this.writer; } }
A decorator is used with an at sign (@
), with the name of the function that you’ll use as a decorator — and it takes three arguments as we just explained.
function autobind(target, value, descriptor) { const fn = descriptor.value; return { configurable: true, get() { return fn.bind(this); } } }
descriptor.value
is the name of the function on which you put the decorator function – in this case, it’s getWriter
itself.
Note that the return value of autobind
is a new object, then getWriter
adopts the return value to its environment.
What’s good about using decorators is that they are reusable. All you need to do after defining the decorator function is merely to write @autobind
on functions.
Here’s another example of making class member properties read-only, which is even easier.
function readonly(target, value, descriptor) { descriptor.writable = false; return descriptor; } class Medium { @readonly signUpDate = '2019-04-23'; } const medium = new Medium(); medium.signUpDate; // 2019-04-23 medium.signUpDate = '1999-11-11'; medium.signUpDate; // 2019-04-23 ^ The value isn't changed!
This time, the descriptor of the property has been changed by setting the writable
property as false
and that is all. Dead simple. Right?
Here’s the comparison of the full code.
class Medium { constructor(writer) { this.writer = writer; } getWriter() { console.log(this.writer); } } const medium = new Medium('Jane'); const fakeMedium = { writer: 'Fake Jane', getWriter: medium.getWriter, }; medium.getWriter(); // Jane fakeMedium.getWriter(); // Fake Jane /* Do auto-binding job for the same values */ let isDefining; let fn = fakeMedium.getWriter; Object.defineProperty(fakeMedium, 'getWriter', { get() { if (isDefining) { return fn; } isDefining = true; const boundFn = this.getWriter.bind(medium); isDefining = false; return boundFn; } }); medium.getWriter(); // Jane fakeMedium.getWriter(); // Jane
function autobind(target, value, descriptor) { const fn = descriptor.value; return { configurable: true, get() { return fn.bind(this); } } } class Medium { constructor(writer) { this.writer = writer; } @autobind getWriter() { console.log(this.writer); } } const medium = new Medium('Jane'); const fakeMedium = { writer: 'Fake Jane', getWriter: medium.getWriter, }; medium.getWriter(); // Jane fakeMedium.getWriter(); // Jane
Try it out by yourself!
A decorator is very useful, powerful, amazing, and remarkable. Honestly, we don’t see any reason to say no to use this awesome feature.
But, remember that it’s still at stage 2 and the way we used this in this post is more like Babel’s style, not the currently proposed one at stage 2. So, things might be different, like how to use it or what you can actually do with it.
So, we absolutely recommend you use this feature with the appropriate Babel configurations for your project but we also want to mention to keep an eye on this feature in TC39.
For more information and to develop your web app using front-end technology, Hire Front-End Developer from us as we give you a high-quality solution by utilizing all the latest tools and advanced technology. E-mail us any clock at – hello@hkinfosoft.com or Skype us: “hkinfosoft“. To develop your custom website using JS, please visit our technology page.
Content Source:
57 Sherway St,
Stoney Creek, ON
L8J 0J3
606, Suvas Scala,
S P Ring Road, Nikol,
Ahmedabad 380049
1131 Baycrest Drive,
Wesley Chapel,
FL 33544
57 Sherway St,
Stoney Creek, ON
L8J 0J3
606, Suvas Scala,
S P Ring Road, Nikol,
Ahmedabad 380049
1131 Baycrest Drive,
Wesley Chapel,
FL 33544
© 2024 — HK Infosoft. All Rights Reserved.
© 2024 — HK Infosoft. All Rights Reserved.
T&C | Privacy Policy | Sitemap