This content originally appeared on Level Up Coding - Medium and was authored by Maxwell
Reconnecting with JavaScript Design Patterns from ES6: Decorator Pattern

1. What Is the Decorator Pattern
A design pattern that adds new functionality to an existing object without changing its structure is called the Decorator Pattern, which acts as a wrapper around an existing class.
Decorators can be understood as equipment purchased by game characters. For example, heroes in LOL only have basic attack power and magic power when they start the game. However, after purchasing the equipment, when triggering attacks and skills, you can enjoy the output bonus brought by the equipment. We can understand that the purchased equipment decorates the related methods of the hero’s attacks and skills.
2. Decorator pattern in ESnext
There is a Decorator proposal in ESnext, which uses a function starting with @ to decorate classes and their properties and methods in ES6.
At present, the syntax of Decorator is only a proposal. If you want to use the decorator mode now, you need to install and cooperate with babel + webpack and implement it in combination with plugins.
npm install dependencies
npm install babel-core babel-loader babel-plugin-transform-decorators babel-plugin-transform-decorators-legacy babel-preset-env
Configure the .babelrc file
{
"presets": ["env"],
"plugins": ["transform-decorators-legacy"]
}
Add babel-loader in webpack.config.js
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
],
}
If you IDE is Visual Studio Code, you may also need to add the following tsconfig.json file in the project root directory to organize a ts check error.
{
"compilerOptions": {
"experimentalDecorators": true,
"allowJs": true,
"lib": [
"es6"
],
}
}
Below I will implement 3 decorators, @autobind, @debounce, @deprecate.
2.1 @autobind implements this to point to the original object
In JavaScript, the problem of this pointing has always been a common topic. During the use of frameworks such as Vue or React, newbies are likely to accidentally lose the pointing of this and cause method invocation errors. For example the following code:
class Person {
getPerson() {
return this;
}
}
let person = new Person();
let { getPerson } = person;
console.log(getPerson() === person); // false
In the above code, this in the getPerson method points to an instance of the Person class by default, but if Person is extracted by destructuring assignment, then this points to undefined. So the final print result is false.
At this point, we can implement an autobind function to decorate the getPerson method, so that this always points to an instance of Person.
function autobind(target, key, descriptor) {
var fn = descriptor.value;
var configurable = descriptor.configurable;
var enumerable = descriptor.enumerable;
// return descriptor
return {
configurable: configurable,
enumerable: enumerable,
get: function get() {
// bind this
var boundFn = fn.bind(this);
// Use Object.defineProperty to redefine the method
Object.defineProperty(this, key, {
configurable: true,
writable: true,
enumerable: false,
value: boundFn
})
return boundFn;
}
}
}
We implement the binding of this through bind, and use Object.defineProperty to rewrite the method in get, and define the value as the boundFn function bound by bind, so that this always points to the instance. Next we decorate and call the getPerson method.
class Person {
@autobind
getPerson() {
return this;
}
}
let person = new Person();
let { getPerson } = person;
console.log(getPerson() === person); // true
2.2 @debounce implements debounce function
Debounce function has many applications in front-end projects, such as manipulating the DOM in events such as resize or scroll, or implementing real-time ajax search for user input, which will be triggered frequently. The former will have an intuitive effect on browser performance. The latter will put a lot of pressure on the server. We expect such high-frequency continuous triggering events to respond after the triggering ends. This is the application of function anti-shake.
class Editor {
constructor() {
this.content = '';
}
updateContent(content) {
console.log(content);
this.content = content;
// There are some performance-consuming operations behind
}
}
const editor1 = new Editor();
editor1.updateContent(1);
setTimeout(() => { editor1.updateContent(2); }, 400);
const editor2= new Editor();
editor2.updateContent(3);
setTimeout(() => { editor2.updateContent(4); }, 600);
// result: 1 3 2 4
In the above code, we define the Editor class, where the updateContent method will be executed when the user inputs and may have some performance-consuming DOM operations. Here we print the incoming parameters inside the method to verify the calling process. You can see that the results of the 4 calls are 1 3 2 4 respectively.
Below we implement a debounce function that passes in a numeric timeout parameter.
function debounce(timeout) {
const instanceMap = new Map();
// Create a Map data structure with the instantiated object as the key
return function (target, key, descriptor) {
return Object.assign({}, descriptor, {
value: function value() {
clearTimeout(instanceMap.get(this));
instanceMap.set(this, setTimeout(() => {
descriptor.value.apply(this, arguments);
instanceMap.set(this, null);
}, timeout));
}
})
}
}
In the above method, we use the Map data structure provided by ES6 to implement the mapping between instantiated objects and delayers. Inside the function, first clear the delayer, and then set the delay execution function. This is a common way to implement debounce. Let’s test the debounce decorator.
class Editor {
constructor() {
this.content = '';
}
@debounce(500)
updateContent(content) {
console.log(content);
this.content = content;
}
}
const editor1 = new Editor();
editor1.updateContent(1);
setTimeout(() => { editor1.updateContent(2); }, 400);
const editor2= new Editor();
editor2.updateContent(3);
setTimeout(() => { editor2.updateContent(4); }, 600);
//result: 3 2 4
The updateContent method is called 4 times above, and the print result is 3 2 4. 1 is not printed because it is called repeatedly within 400ms, which is in line with our expectation of a parameter of 500.
2.3 @deprecate implements warning prompts
In the process of using third-party libraries, we will encounter some warnings in the console from time to time. These warnings are used to remind developers that the methods called will be deprecated in the next version. Such a line of printing information may be our usual practice to add a line of code inside the method, which is actually not friendly to source code reading and does not conform to the single responsibility principle. It would be much friendlier to add a @deprecate decorator in front of a method that needs to throw a warning to implement the warning.
Let’s implement a @deprecate decorator. In fact, this type of decorator can also be extended to print log decorator @log, report information decorator @fetchInfo, etc.
function deprecate(deprecatedObj) {
return function(target, key, descriptor) {
const deprecatedInfo = deprecatedObj.info;
const deprecatedUrl = deprecatedObj.url;
// Warning message
const txt = `DEPRECATION ${target.constructor.name}#${key}: ${deprecatedInfo}. ${deprecatedUrl ? 'See '+ deprecatedUrl + ' for more detail' : ''}`;
return Object.assign({}, descriptor, {
value: function value() {
// print warning message
console.warn(txt);
descriptor.value.apply(this, arguments);
}
})
}
}
The above deprecate function accepts an object parameter, which has two key values: info and url, where info is filled with warning information, and url is the optional details webpage address. Let’s add this decorator to the deprecatedMethod method of a library named MyLib!
class MyLib {
@deprecate({
info: 'The methods will be deprecated in next version',
url: 'https://www.medium.com'
})
deprecatedMethod(txt) {
console.log(txt)
}
}
const lib = new MyLib();
lib.deprecatedMethod('Called a method that will be removed in the next version');
3. Summary
Implementing the decorator pattern through the decorator in ESnext not only has the function of extending the functions of the class, but also plays a prompting role in the process of reading the source code. The example mentioned above is just a simple encapsulation combined with the new syntax of the decorator and the decorator pattern, and should not be used in a production environment. If you have now realized the benefits of the decorator pattern and want to use it a lot in your project, take a look at the core-decorators library, which encapsulates many commonly used decorators.
For other articles on design patterns, read the following articles,if you are interested in my articles, you can follow me on Medium or Twitter.
- JavaScript Design Patterns: Factory Pattern
- JavaScript Design Patterns: Observer Pattern
- JavaScript Design Patterns: Strategy Pattern
- JavaScript Design Patterns : Singleton Pattern
JavaScript Design Patterns: Decorator Pattern was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Maxwell

Maxwell | Sciencx (2022-10-30T13:20:03+00:00) JavaScript Design Patterns: Decorator Pattern. Retrieved from https://www.scien.cx/2022/10/30/javascript-design-patterns-decorator-pattern/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.