current position:Home>Decorator pattern of JavaScript Design Pattern

Decorator pattern of JavaScript Design Pattern

2021-08-29 07:13:18 Niu Niu_ lz

This is my participation 8 The fourth of the yuegengwen challenge 12 God , Check out the activity details :8 Yuegengwen challenge

A way of dynamically adding responsibility to an object is called decorator (decorator) Pattern . The decorator pattern can be based on not changing the object itself , Adding responsibilities to objects dynamically during program execution . Compared with inheritance , Decorator is a more light and flexible way .

JavaScript The decorator of

JavaScript It's quite easy for languages to change objects dynamically , We can directly override an object or a method of an object , No need to use “ class ” To implement decorator mode , The code is as follows :

var plane = {
  fire: function () {
    console.log(' Fire ordinary bullets ');
  }
}
var missileDecorator = function () {
  console.log(' Launch missiles ');
}
var atomDecorator = function () {
  console.log(' Launch the atomic bomb ');
}
var fire1 = plane.fire;
plane.fire = function () {
  fire1();
  missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function () {
  fire2();
  atomDecorator();
}
plane.fire();
//  Output, respectively, :  Fire ordinary bullets 、 Launch missiles 、 Launch the atomic bomb 
 Copy code 

Decoration function

stay JavaScript You can easily extend properties and methods to an object , But it's hard to do without changing the source code of a function , Add some extra functionality to this function . While the code is running , It's hard to cut into the execution environment of a function .

To add some functionality to a function , The simplest and crudest way is to rewrite the function directly , But this is the worst way , It's a direct violation of openness - Closed principle :

var a = function () {
  alert(1);
}
//  Change to 
var a = function () {
  alert(1);
  alert(2);
}
 Copy code 

The above example shows that a function can be rewritten by saving the original reference :

var a = function () {
  alert(1);
}

var _a = a;
a = function () {
  _a();
  alert(a);
}

a();
 Copy code 

This is a very common practice in actual development , For example, we want to give window binding onload event , But I'm not sure if this event has been bound by others , To avoid overwriting the previous window.onload Behavior in functions , We usually save the original window.onload, Put it in a new window.onload Internal execution :

window.onload = function () {
  alert(1);
}
var _onload = window.onload || function () { };
window.onload = function () {
  _onload();
  alert(2);
}
 Copy code 

But there are two problems with this approach :

  • Must maintain _onload This intermediate variable , Although it doesn't look impressive , But if the decoration chain of the function is long , Or there are more functions to decorate , There will be more and more of these intermediate variables .
  • this The question of being hijacked , stay window.onload There is no such trouble in the example of , Because calling ordinary functions _onload when ,this Point to window, Follow call window.onload At the same .

use AOP Decoration function

First of all give Function.prototype.before Methods and Function.prototype.after Method :

Function.prototype.before = function (beforefn) {
  var __self = this; //  Save the reference of the original function 
  return function () { //  Returns... That contains the original function and the new function " agent " function 
    beforefn.apply(this, arguments); //  Execute the new function , And guarantee  this  Not hijacked , The parameters accepted by the new function will also be passed into the original function intact , The new function executes before the original function 
    return __self.apply(this, arguments); //  Execute the original function and return the execution result of the original function , And guarantee  this  Not hijacked 
  }
}
Function.prototype.after = function (afterfn) {
  var __self = this;
  return function () {
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};
 Copy code 

Function.prototype.before Take a function as an argument , This function is the newly added function , It loads the newly added function code .

Next, put the current this Save up , This this Point to the original function , And then return a “ agent ” function , This “ agent ” Functions are just structured like proxies , Does not assume the responsibility of agency ( For example, controlling the access of objects ). Its job is to forward the request to the newly added function and the original function respectively , And be responsible for ensuring their execution sequence , Let the newly added function execute before the original function ( Front decoration ), In this way, the effect of dynamic decoration is realized . adopt Function.prototype.apply To dynamically pass in the correct this, Ensures that the function is decorated ,this It won't be hijacked .

Function.prototype.after The principle of Function.prototype.before As like as two peas , The only difference is that the newly added function is executed after the original function is executed .

So let's see , Use Function.prototype.before To add new window.onload How simple the event is :

window.onload = function () {
  alert(1);
}
window.onload = (window.onload || function () { }).after(function () {
  alert(2);
}).after(function () {
  alert(3);
}).after(function () {
  alert(4);
});
 Copy code 

above AOP The realization is in Function.prototype Add before and after Method , But many people don't like this way of polluting the prototype , Then we can do something , Pass both the original function and the new function as parameters before perhaps after Method :

var before = function (fn, beforefn) {
  return function () {
    beforefn.apply(this, arguments);
    return fn.apply(this, arguments);
  }
}
var a = before(
  function () { alert(3) },
  function () { alert(2) }
)

a = before(a, function () { alert(1) })
a();
 Copy code 

AOP Application example

use AOP The technique of decorating functions is very useful in practical development . Whether it's writing business code , Still at the framework level , We can all divide behavior into more granular functions according to responsibilities , They are then combined by decoration , This helps us to write a loosely coupled and highly reusable system .

Data statistics report

Separate business code and data statistics code , In any language , All are AOP One of the classic applications of . For example, there is a login in the page button, Click on this. button The login floating layer will pop up , At the same time, data shall be reported , To count how many users have clicked this login button:

<html>
<button tag="login" id="button"> Click to open the login floating layer </button>
<script> var showLogin = function () { console.log(' Open login float '); log(this.getAttribute('tag')); } var log = function (tag) { console.log(' The report label is : ' + tag); // (new Image).src = 'http:// xxx.com/report?tag=' + tag; //  The real reporting code is slightly  } document.getElementById('button').onclick = showLogin; </script>

</html>
 Copy code 

We see in the showLogin In the function , Be responsible for opening the login floating layer , Also responsible for data reporting , This is a function of two levels , Here it is coupled in a function . Use AOP After separation , The code is as follows :

<html>
<button tag="login" id="button"> Click to open the login floating layer </button>
<script> Function.prototype.after = function (afterfn) { var __self = this; return function () { var ret = __self.apply(this, arguments); afterfn.apply(this, arguments); return ret; } }; var showLogin = function () { console.log(' Open login float '); } var log = function () { console.log(' The report label is : ' + this.getAttribute('tag')); } showLogin = showLogin.after(log); //  Open the login floating layer and report the data  document.getElementById('button').onclick = showLogin; </script>

</html>
 Copy code 

use AOP Changing the parameters of a function dynamically

We need to give ajax Request to add a token. Now there is one for generating token Function of :

var getToken = function () {
  return 'Token'
}
 Copy code 

Now let's give everyone ajax Add... To the request token Parameters :

var ajax = function(type, url, param) {
  param = param || {}
  param.token = getToken
  //  send out  ajax  The code is omitted ……
}
 Copy code 

Although the problem has been solved , But our ajax Functions become relatively rigid , Each from ajax All requests made in the function are automatically brought token Parameters , Although there are no problems in the current project , But if you migrate this function to other projects in the future , Or put it in an open source library for others to use ,token Parameters will be redundant .

To solve this problem , The first ajax Function is reduced to a clean function :

var ajax= function( type, url, param ){ 
  console.log(param); 
  //  send out  ajax  The requested code is omitted ……
};
 Copy code 

And then put token Parameters through Function.prototyte.before Decorate to ajax The parameters of the function param In the object :

var getToken = function(){ 
  return 'token';
}
ajax = ajax.before(function( type, url, param ){ 
  param.Token = getToken();
});
ajax( 'get', 'http://xxx.com/userinfo', { name: 'sven' } );
 Copy code 

from ajax Function to print log You can see ,token Parameters have been attached to ajax Of the requested parameters : {name: "sven", Token: "token"}

You can clearly see that , use AOP The way to ajax Function dynamic decoration token Parameters , To ensure the ajax Function is a relatively pure function , Improved ajax Reusability of functions , When it was moved to other projects , No modification required .

Plug in form validation

Many of us have written a lot of form validation code , In a Web In the project , There may be a lot of forms , If registered 、 Sign in 、 Modify user information, etc . Before the form data is submitted to the background , Often do some verification , For example, when logging in, you need to verify whether the user name and password are empty .

What we need to do now is to separate verification input and submission ajax Requested code , To make validata and formSubmit Completely separate . First, rewrite Function.prototype.before, If beforefn The execution result of the false, Indicates that the following original functions will not be executed :

Function.prototype.before = function (beforefn) {
  var __self = this
  return function () {
    if (beforefn.apply(this, arguments) === false) {
      // beforefn  return  false  The situation is direct  return, Don't execute the original function later  
      return;
    }
    return __self.apply(this, arguments)
  }
}

var validata = function () {
  if (username.value === '') {
    alert(' The username cannot be empty ')
    return false
  }
  if (password.value === '') {
    alert(' The password cannot be empty ')
    return false
  }
}

var formSubmit = function () {
  var param = {
    username: username.value,
    password: password.value
  }
  ajax('http://xxx.com/login', param)
}
formSubmit = formSubmit.before(validata)
submitBtn.onclick = function () {
  formSubmit()
}
 Copy code 

It is worth noting that , Because the function passes Function.prototype.before perhaps Function.prototype.after After being decorated , What's returned is actually a new function , If you save some properties on the original function , Then these attributes will be lost . The code is as follows :

var func = function(){ 
  alert( 1 );
}
func.a = 'a';

func = func.after( function(){ alert( 2 ) });

alert ( func.a ); //  Output :undefined
 Copy code 

in addition , This decoration also overlaps the scope of the function , If the decorated chain is too long , Performance will also be affected .

Decorator mode and agent mode

The structure of decorator mode and agent mode look very similar , Both modes Describes how to provide some degree of indirect reference to objects , Their implementation part retains a reference to another object , And send a request to that object .

The difference between agent mode and decorator mode is :

  • Their intention and design purpose .
    • The purpose of the agency model is , When direct access to ontology is inconvenient or does not meet the needs , Provide an alternative to this ontology . Ontology defines key functions , The agent provides or denies access to it , Or do something extra before accessing the ontology .
    • The role of decorator pattern is to dynamically add behavior to objects .
  • The agency model emphasizes a relationship (Proxy The relationship with its entity ), This relationship can be expressed statically , in other words , This relationship can be determined from the beginning . Decorator mode is used when you can't determine all the functions of the object at first .
  • Agent mode usually has only one layer of agents - Ontology reference , The decorator model often forms a long decorative chain .

And finally

If this article is helpful to you , Or if there's some inspiration , Give me some praise and pay attention to , Your support is my biggest motivation for writing , Thank you for your support .

copyright notice
author[Niu Niu_ lz],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210829071312749p.html

Random recommended