1. Decoration mode
We all know about design patterns, there are many series of tutorials on the Internet, such as JS design patterns and so on.
Only the decorator pattern and the concept of how to use ES7 decorators are shared here
1.1. Decoration Pattern V.S. Adapter pattern
The decorator Pattern and the adapter Pattern are both “Wrapper patterns” designed to encapsulate other objects, but their shapes are quite different.
- Adapter mode we use a lot of scenarios, such as connecting to different databases, you need to wrap the existing module interface to adapt to the database — like your phone uses a adapter to adapt to a socket;
- Decorative patternIn contrast, simply packaging existing modules to make them “more beautiful” does not affect the functionality of the original interface — like adding a cover to a phone, which does not affect the original functions of the phone such as calling, charging, etc.
See also: Design Pattern — Decorator
1.2. Decorator pattern scenarios — AOP oriented programming
Application is AOP programming classic decorative pattern, such as “logging system”, the function of logging system is the behavior of the recording system operation, it does not affect the function of the original system on the basis of increase the record – if you wear a smart hand ring, does not affect your daily routine daily life, but now you have our behavior records every day.
More abstractly, you can think of it as a layer of data flowfilter
So typical uses of AOP include security checking, caching, debugging, persistence, and so on. May refer toSpring AOP principles and various application scenarios.
2. Use ES7 decorators
A decorator attribute has been added to ES7, borrowed from Python, see Decorators in ES7.
Let’s use iron Man as an example to show how to use ES7 decorators.
Take Iron Man for example. Iron Man is essentially a man, but he has to decorate a lot of weapons to make him look like that, but no matter how decorated he is, he is still a man.
Our example scenario looks like this
- First, create a normal
Man
Class, it has 2 resistance, 3 damage, and 3 health. - Then we put iron Man armor on him, so his resistance increases by 100, to 102;
- [Fixed] Wearing a beam glove increases its damage by 50 to 53.
- Finally, let him increase his “flight” ability
2.1. [Demo 1] Method decoration: Equip armor
Create Man class:
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
init(def,atk,hp){
this.def = def; / / defense value
this.atk = atk; / / damage
this.hp = hp; / / health
}
toString(){
return 'Defense force:The ${this.def}, attack power:The ${this.atk}HP:The ${this.hp}`; }}var tony = new Man();
console.log('Current status ===>${tony}`);
// Output: Current status ===> Defense :2, damage :3, Health :3
Copy the code
Run the code directly in babeljs. IO /repl/ and check Experimental and Evaluate
Create a decorateArmour method to assemble iron Man’s armor — note that decorateArmour is decorated on method init.
function decorateArmour(target, key, descriptor) {
const method = descriptor.value;
let moreDef = 100;
let ret;
descriptor.value = (. args) = >{
args[0] += moreDef;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
@decorateArmour
init(def,atk,hp){
this.def = def; / / defense value
this.atk = atk; / / damage
this.hp = hp; / / health
}
toString(){
return 'Defense force:The ${this.def}, attack power:The ${this.atk}HP:The ${this.hp}`; }}var tony = new Man();
console.log('Current status ===>${tony}`);
// Output: Current status ===> Defense :102, damage :3, Health :3
Copy the code
Let’s take a look at the output. Defense did increase by 100. Looks like the armor is working.
– 1. DecorateArmour method has these three parameters. Can I change it? – 2. DecorateArmour method why does descriptor return
Here are personal answers for reference:
- The nature of Decorators is to take advantage of ES5’s Object.defineProperty property. These three parameters are identical to the object. defineProperty parameter and therefore cannot be changed. See ES7 JavaScript Decorators for a detailed analysis
- You can seeBable converted“, and one of the sentences is
descriptor = decorator(target, key, descriptor) || descriptor;
So far, I won’t expand in detail here, but you can look at the context of this line of code (references also refer to the explanation of this code).
2.2 [Demo 2] Decorative overlay: added beam gloves
In the example above, we succeeded in adding “armor” for ordinary people; Now I want to add the “Beam Glove” to him, hopefully adding an additional 50 defense points.
Copy the decorateArmour method, rename it to decorateLight, and modify the defense value properties:
function decorateLight(target, key, descriptor) {
const method = descriptor.value;
let moreAtk = 50;
let ret;
descriptor.value = (. args) = >{
args[1] += moreAtk;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
Copy the code
Add the decorator syntax directly to the init method:
. @decorateArmour @decorateLightinit(def,atk,hp){
this.def = def; / / defense value
this.atk = atk; / / damage
this.hp = hp; / / health}...Copy the code
The final code looks like this:
.function decorateLight(target, key, descriptor) {
const method = descriptor.value;
let moreAtk = 50;
let ret;
descriptor.value = (. args) = >{
args[1] += moreAtk;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
@decorateArmour
@decorateLight
init(def,atk,hp){
this.def = def; / / defense value
this.atk = atk; / / damage
this.hp = hp; / / health}... }var tony = new Man();
console.log('Current status ===>${tony}`);
// Output: Current status ===> Defense :102, damage :53, Health :3
Copy the code
This is where you can see the advantage of decorator mode. It can superimpose a method and is very intrusive to the original class. Just add a line of @decoratelight and you can easily add and delete. (It can also be reused)
2.3 [Demo 3] Class decoration: increased flying ability
According to the article decoration mode, there are two kinds of decoration mode: pure decoration mode and translucent decoration mode.
The above two demos should use a purely decorative pattern that does not add interfaces to existing classes; The next demo is to add the ability to “fly” to ordinary people, which is like adding a method to the class. It’s a semi-transparent decoration mode, kind of like adapter mode.
Step 1: Add a method:
function addFly(canFly){
return function(target){
target.canFly = canFly;
let extra = canFly ? '(Skill bonus: Flying Ability)' : ' ';
let method = target.prototype.toString;
target.prototype.toString = (. args) = >{
return method.apply(target.prototype,args) + extra;
}
returntarget; }}Copy the code
Step 2: This method decorates the class directly:
./ / 3
function addFly(canFly){
return function(target){
target.canFly = canFly;
let extra = canFly ? '(Skill bonus: Flying Ability)' : ' ';
let method = target.prototype.toString;
target.prototype.toString = (. args) = >{
return method.apply(target.prototype,args) + extra;
}
return target;
}
}
@addFly(true)
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
@decorateArmour
@decorateLight
init(def,atk,hp){
this.def = def; / / defense value
this.atk = atk; / / damage
this.hp = hp; / / health}... }...console.log('Current status ===>${tony}`);
// Output: Current status ===> Defense :102, damage :53, Health :3(Skill bonus: Flight)
Copy the code
The first argument (target) that a decorator acting on a method receives is the class prototype; If a decorator is applied to a class, its first argument, target, is the class itself. (See Decorators in ES7)
Use native JS to implement decorator patterns
JavaScript Design Patterns: Decorator Patterns. This is a good article to read.
Here’s a rewrite of the Demo 1 scenario above using ES5 to briefly outline the key points:
- Man is the concrete class, and decorators are the base Decorator class for Man
- The concrete Decorator class DecorateArmour typically inherits from the Decorator base class using the Prototype inheritance;
- Based on the IOC (Inversion of Control) idea, decorators accept Man classes instead of creating them themselves;
The final code is:
// First we create a base class
function Man(){
this.def = 2;
this.atk = 3;
this.hp = 3;
}
Decorators also need to implement these methods, following the Man interface
Man.prototype={
toString:function(){
return 'Defense force:The ${this.def}, attack power:The ${this.atk}HP:The ${this.hp}`; }}// Create a decorator that takes a Man object as an argument.
var Decorator = function(man){
this.man = man;
}
// Decorators implement these same methods
Decorator.prototype.toString = function(){
return this.man.toString();
}
// Inherits from the decorator object
// Create a concrete decorator that also receives the Man argument
var DecorateArmour = function(man){
var moreDef = 100;
man.def += moreDef;
Decorator.call(this,man);
}
DecorateArmour.prototype = new Decorator();
// Next we will create a decorator object for each function, override the parent method, and add the desired function.
DecorateArmour.prototype.toString = function(){
return this.man.toString();
}
// Notice how this is called
// The constructor is equivalent to a "filter", which is faceted
var tony = new Man();
tony = new DecorateArmour(tony);
console.log('Current status ===>${tony}`);
// Output: Current status ===> Defense :102, damage :3, Health :3
Copy the code
4. Classic implementation: Logger
The classic application of AOP is logging system, so let’s also use ES7 syntax to build a logging system for Iron Man.
Here is the final code:
/** * Created by jscon on 15/10/16. */
let log = (type) = > {
return (target, name, descriptor) = > {
const method = descriptor.value;
descriptor.value = (. args) = > {
console.info(` (${type}) is executing:${name}(${args}) = ?`);
let ret;
try {
ret = method.apply(target, args);
console.info(` (${type}Success:${name}(${args}) = >${ret}`);
} catch (error) {
console.error(` (${type}Failure) :${name}(${args}) = >${error}`);
}
returnret; }}}class IronMan {
@log('IronMan Self-inspection Phase ')
check(){
return 'All checked out';
}
@log('IronMan Attack Stage ')
attack(){
return 'Knock down the enemy';
}
@log('IronMan body error ')
error(){
throw 'Something is wrong! '; }}var tony = new IronMan();
tony.check();
tony.attack();
tony.error();
/ / output:
// (IronMan self-check phase) executing: check() =?
// (IronMan self-check stage) Successful: check() => Check complete
// (IronMan attack stage) executing: attack() =?
// (IronMan attack stage) Success: Attack () => Knock down enemies
// (IronMan error) executing: error() =?
// (IronMan) error: error() => Something is wrong!
Copy the code
The key to Logger methods is: – First use const method = Description. value; Extract the original method to ensure the purity of the original method; – in the try.. The catch statement calls ret = method.apply(target, args); Log reporting before and after invocation; – Finally returns return ret; The result of the original call
I believe that this set of ideas will provide a good reference for the implementation of AOP model.
5, extension,
5.1. Based on the factory model
When you want an Iron Man with three functions, use the new operator to create four objects. This is tedious and annoying, so we decided to create an Iron Man with all the features in one method.
This is where the factory pattern comes in. The official definition of the factory pattern is to instantiate the member objects of a class in a subclass. For example, defining a decorateIronMan (Person,feature) method that accepts a Person object (instead of initializing it itself) is pipelining.
JavaScript Design Patterns: Factory Patterns gives you a detailed approach to how to combine decorator and factory patterns to improve code performance.
5.2, the pure – render – a decorator
ShouldComponentUpdate () is shouldComponentUpdate(), which is used to avoid unnecessary rerenders. Can directly use the official React. Addons. PureRenderMixin, or use a pure – render – decorator, it is to use the ES7 decorator:
import {Component} from 'react';
import pureRender from 'pure-render-decorator';
@pureRender
class Test extends Component {
render() {
return <div></div>; }}Copy the code
Equivalent to:
var React = require('react');
var PureRenderMixin = require('react-addons-pure-render-mixin');
var Test = React.createClass({
mixins: [
PureRenderMixin
],
render: function() {
return <div></div>; }});Copy the code
6. Want to use it now?
The decorator is just a suggestion for now, but thanks to Babel, we can try it out now. First, install Babel:
npm install babel -g
Copy the code
Then, start the decorator:
babel --optional es7.decorators foo.js > foo.es5.js
Copy the code
Babel also offers an online REPL, so check experimental.
Set up Babel in WebStorm
Step 1: Install the Babel module globally
npm install -g babel
Copy the code
Step 2: Set the scope.
Naming the scope:
Adds the file to the current scope:
Step 3: Set the ES version
Step 4: Add watcher
In the arguments can fill in: $FilePathRelativeToProjectRoot $- stage – out – the file $FileNameWithoutExtension $- es5. Js $FilePath $
.
If source-map is required, add the –source-map option, $FileNameWithoutExtension$-es5.js:$FileNameWithoutExtension$-es5.js.map
See Babel CLI for more Settings
7,
Although it is a feature of ES7, with Babel in vogue today, we can use it with Babel. Decorators can be used using the Babel command-line tool or the Babel plug-in for Grunt, gulp, or Webpack.
The above code can be run directly in babeljs. IO /repl/ to see the result;
For a more interesting way to play ES7 Decorators, you can refer to common Decorators implemented by great people: core-decorators. And Raganwald’s how to implement mixins with Decorators.
reference
- Decorators in ES7: The decorator pattern lets you extend existing functions by wrapping existing methods.
- JavaScript Design Patterns: Decorator Patterns: Highly recommended, this series will give you a better understanding of the application of design patterns in JS.
- Example of ES7 Decorators implementing AOP: How to implement a simple AOP.
- ES7 JavaScript Decorators: Explain the principle behind ES7 Decorators by using the Object.defineProperty method.
- How To Set Up the Babel Plugin in WebStorm
- Rest Parameters and Parameter Defaults: ES6 gives us a new way to create variable-parameter functions, Rest parameters and parameter defaults
- Exploring ES2016 Decorators: This is a complete tutorial, Exploring ES2016 Decorators.
- Traits with ES7 Decorators: It’s an introduction
traits-decorator
Module;
Copy the code