The singleton pattern

Lazy singleton encapsulation

var getSingle = function (fn) {
    var result;
    return function () {
        return result || ( result = fn.apply(this.arguments)); }}Copy the code

The strategy pattern

Definition: Define a set of algorithms, encapsulate them one by one, and make them interchangeable.

In addition to algorithms, policy patterns can also be used to encapsulate a series of business rules, such as form validation, which can correspond to form validation rules and error messages. After defining a rule policy, enter the rule name and error message for verification.

The proxy pattern

Definition: The proxy pattern provides a proxy or placeholder for an object to control access to it.

Protected proxy: can add conditions to filter, reject part of the request, control objects with different permissions to the target object access virtual proxy: time-consuming or can be merged operations, delay processing, triggered at the appropriate time. For example: loading images, merging interface requests, etc.

Examples of virtual proxies for merge requests:

var synchromousFile = function ( id ) {
    console.log('Start synchronizing files with id' + id);
};

var proxySynchronousFile = (function () {
    var cache = [], // Save the set of ids that need to be synchronized over a period of time
    timer;

    return function( id ) {
        cache.push(id);
        if ( timer ) {
            return;
        }

        timer = setTimeout(function() {
            synchromousFile( cache.join(', ')); // Send the set of ids to be synchronized to the ontology after 2 seconds
            clearTimeout( timer );
            timer = null;
            cache.length = 0; // Clear the ID collection
        }, 2000)}}) ();Copy the code

Publish and subscribe

Defines a one-to-many dependency between objects in which all dependent objects are notified when the state of one object changes.

Advantages: one is the time decoupling, the other is the decoupling between objects. Disadvantages: Too much use can make connections between modules hidden behind the scenes.

The following are global publish-subscribe objects:

var Event = (function () {

    var clientList = {},
        listen,
        trigger,
        remove;
    
    listen = function (key, fn) {
        if(! clientList[ key ]) { clientList[ key ] = []; } clientList[ key ].push( fn ); }; trigger =function () {
        var key = Array.prototype.shift.call( arguments ),
            fns = clientList[ key ];
            if ( !fns || fns.length === 0) {
                return false;
            }
            for ( var i = 0, fn; fn = fns[ i++ ]; ) {
                fn.apply( this.arguments);
            }
    }

    remove = function ( key, fn ) {
        var fns = clientList[ key ];
        if ( !fns ) {
            return false;
        }
        if ( !fn ) {
            fns && ( fns.length = 0);
        } else {
            for ( var l = fns.length - 1; l >= 0; l--) {
                var _fn = fns[ l ];
                if ( _fn === fn ) {
                    fns.splice( l, 1); }}}};return {
        listen: listen,
        trigger: trigger,
        remove: remove
    }
})();
Copy the code

Command mode

Definition: An instruction to do something specific.

The most common scenario: sometimes you need to send a request to some object, but you don’t know who the recipient of the request is or what the requested action is. At this point, you want to design the program in a loosely coupled way so that the request sender and the request receiver can decouple from each other. Command mode supports undo, queuing, macro commands, and so on.

Command mode using closures:

var setCommand = function ( button, func ) {
    button.onclick = function () { func(); }};var MenuBar = {
    refresh: function () {
        console.log( The 'refresh'); }};var RefreshMenuBarCommand = function ( receiver ) {
    return function () { receiver.refresh(); }};var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar );

setCommand( button1, refreshMenuBarCommand);
Copy the code

Portfolio model

The composition pattern groups objects into a tree structure to represent a partial-whole hierarchy. In addition to being used to represent tree structures, another benefit of the composite pattern is the consistent use of individual and composite objects through the polymorphic representation of objects, as described below.

  • Represents a tree structure. By reviewing the examples above, it is easy to see one advantage of the composite pattern: By calling the execute method of the composite object, the program will recursively call the execute method of the leaf object below the composite object. Therefore, our universal remote control only needs one operation to complete the following tasks: closing the door, opening the computer, and logging in QQ. The composite pattern is a very convenient way to describe object partial-whole hierarchies.
  • Object polymorphism is used to treat combined objects and single objects uniformly. The polymorphic representation of an object enables clients to ignore the differences between a composite object and a single object. In the composite pattern, the customer uses all objects in the composite structure uniformly, regardless of whether it is a composite object or a single object.
// Create a macro command
var MacroCommand = function(){
    return {
        // List of subcommands of macro commands
        commandsList: [].// Add commands to the subcommand list
        add: function( command ){
            this.commandsList.push( command );
        },
        // Execute the commands in the subcommand list
        execute: function(){
            for ( var i = 0, command; command = this.commandsList[ i++ ]; ) { command.execute(); }}}}; <! -- Turn on air conditioner command -->var openAcCommand = {
    execute: function(){
        console.log( 'Turn on the air conditioner'); }}; <! Turn on the TV and stereo -->var openTvCommand = {
    execute: function(){
        console.log( 'Turn on the TV'); }};var openSoundCommand = {
    execute: function(){
        console.log( 'Turn on the stereo'); }};// Create a macro command
var macroCommand1 = MacroCommand();
// Put the TV on in the macro command
macroCommand1.add(openTvCommand)
// Put the open sound into the macro commandmacroCommand1.add(openSoundCommand) <! -- Close the door, turn on the computer and type the command to log in QQ -->var closeDoorCommand = {
    execute: function(){
        console.log( 'shut down'); }};var openPcCommand = {
    execute: function(){
        console.log( 'Turn on the computer'); }};var openQQCommand = {
    execute: function(){
        console.log( 'login QQ'); }};// Create a macro command
var macroCommand2 = MacroCommand();
// Put the close door command into the macro command
macroCommand2.add( closeDoorCommand );
// Put the open computer command into the macro command
macroCommand2.add( openPcCommand );
// Put the QQ login command into the macro commandmacroCommand2.add( openQQCommand ); <! Put each macro command into a supercommand -->var macroCommand = MacroCommand();
macroCommand.add( openAcCommand );
macroCommand.add( macroCommand1 );
macroCommand.add( macroCommand2 );
Copy the code

The flyweight pattern

The core of the share pattern is the use of sharing technology to effectively support a large number of fine-grained objects, which is very useful if the memory footprint of the system is too high due to the creation of a large number of similar objects.

Share mode requires that the attributes of the object be divided into internal state and external state. How to divide:

  • Internal state is stored inside an object
  • Internal state can be shared by several objects
  • The internal state is independent of the specific scenario and usually does not change
  • The external state depends on and changes according to the specific scenario. The external state cannot be shared.

Example: File upload

var Upload = function(uploadType) {
  this.uploadType = uploadType;
}
​
/* Delete file (internal state) */
Upload.prototype.delFile = function(id) {
  uploadManger.setExternalState(id, this);  // Assemble all external states corresponding to the current ID into the shared object
  // The value is greater than 3000K
  if(this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }
  if(window.confirm("Are you sure you want to delete the file?" + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom); }}/** Factory object instantiation * If a shared object of some internal state has already been created, then return the object directly * otherwise, create a new object */
var UploadFactory = (function() {
    var createdFlyWeightObjs = {};
    return {
        create: function(uploadType) {
            if(createdFlyWeightObjs[ uploadType ]) {
                return createdFlyWeightObjs[ uploadType ]
            }
            return createdFlyWeightObjs[ uploadType ] = newUpload( uploadType ); }}}) ();/* The manager encapsulates external state */
var uploadManger = (function() {
  var uploadDatabase = {};
​
  return {
    add: function(id, uploadType, fileName, fileSize) {
      var flyWeightObj = UploadFactory.create(uploadType);
      var dom = document.createElement('div');
      dom.innerHTML = " file name:" + fileName + ", file size: + fileSize +"</span>"
              + ;
​
      dom.querySelector(".delFile").onclick = function() {
        flyWeightObj.delFile(id);
      };
      document.body.appendChild(dom);
​
      uploadDatabase[id] = {
        fileName: fileName,
        fileSize: fileSize,
        dom: dom
      };
​
      return flyWeightObj;
    },
    setExternalState: function(id, flyWeightObj) {
      var uploadData = uploadDatabase[id];
      for(var i inuploadData) { flyWeightObj[i] = uploadData[i]; }}}; }) ();/* Trigger upload action */
var id = 0;
window.startUpload = function(uploadType, files) {
  for(var i=0,file; file = files[i++];) {
    varuploadObj = uploadManger.add(++id, uploadType, file.fileName, file.fileSize); }};/ * * / test
startUpload("plugin"[{fileName: '1.txt'.fileSize: 1000}, {fileName: '2.txt'.fileSize: 3000}, {fileName: '3.txt'.fileSize: 5000}]); startUpload("flash"[{fileName: '4.txt'.fileSize: 1000}, {fileName: '5.txt'.fileSize: 3000}, {fileName: '6.txt'.fileSize: 5000}]);Copy the code

Chain of Responsibility model

To avoid coupling between the sender and receiver of the request by giving multiple objects the opportunity to process it, link the objects into a chain and pass the request along the chain until one object processes it.

Example: Implementation of an asynchronous responsibility chain

/ / Chain. Prototype. SetNextSuccessor specify the next node in the Chain
/ / Chain. Prototype. PassRequest pass the request to a node
var Chain = function ( fn ) {
    this.fn = fn;
    this.successor = null;
}
Chain.prototype.setNextSuccessor = function ( successor ) {
    return this.successor = successor;
}

// The determination of ret can be used in the synchronous responsibility chain pattern
Chain.prototype.passRequest = function () {
    var ret = this.fn.apply( this.arguments);
    if ( ret === 'nextSuccessor' ) {
        return this.successor && this.successor.passRequest.apply( this.successor, arguments );
    }
    return ret;
}

// In asynchronous mode, ret does not have a return value and cannot be received. Therefore, next callback is used
Chain.prototype.next = function () {
    return this.nextSuccessor && this.successor.passRequest.apply( this.successor, arguments );
}

var fn1 = new Chain(function(){
    console.log( 1 );
    return 'nextSuccessor';
});
var fn2 = new Chain(function(){
    console.log( 2 );
    var self = this;
    setTimeout(function(){
        self.next();
    }, 1000 );
});
var fn3 = new Chain(function(){
    console.log( 3 );
});
fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 );
fn1.passRequest();
Copy the code

Implement the chain of responsibility with AOP

Function.prototype.after = function( fn ){
    var self = this;
    return function(){
        var ret = self.apply( this.arguments );
        if ( ret === 'nextSuccessor') {return fn.apply( this.arguments );
        }
        returnret; }};// Order500YUAN, order200yuan, orderNormal are orderprocessing functions, not listed here
var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1.true.500 ); // Output: 500 yuan deposit pre-order, get 100 coupons
order( 2.true.500 ); // Output: 200 yuan deposit pre-order, get 50 coupons
order( 1.false.500 ); // Output: normal purchase, no coupon
Copy the code

The mediator pattern

The role of the mediator pattern is to decouple objects from each other. By adding a mediator object, all related objects communicate through the mediator object rather than referring to each other, so when an object changes, the mediator object only needs to be notified. The mediator loosens the coupling between objects and can change their interactions independently.

Example: Using a mediator in a game to control game state

Player object generation:

function Player (name, teamColor) {
    this.name = name;
    this.teamColor = teamColor;
    this.state = 'alive';
};

Player.prototype.win = function () {
    console.log(`The ${this.name}won! `);
};

Player.prototype.lose = function () {
    console.log(`The ${this.name}lost! `);
};
// The player dies
Player.prototype.die = function () {
    this.state = 'dead';
    // Send a message to the broker, the player dies
    playerDirector.ReceiveMessage( 'playDead'.this );
}

// Remove the player
Player.prototype.remove = function () {
    // Send a message to the mediator and the player is removed
    playerDirector.ReceiveMessage( 'removePlayer'.this );
}

// Players change teams
Player.prototype.remove = function ( color ) {
    // Send a message to the broker and the player changes teams
    playerDirector.ReceiveMessage( 'changeTeam'.this, color );
}

// Generate the player's factory function
var palyerFactory = function ( name, teamColor ) {
    var newPlayer = new Player( name, teamColor );

    // Send a message to the broker to add new players
    playerDirecotor.ReceiveMessage( 'addPlayer', newPlayer );
    return newPlayer;
}
Copy the code

Implementation of intermediary playerDirector:

  • Leverage the publish-subscribe model. PlayerDirector is implemented as a subscriber, and each player acts as a publisher. Once the player status changes, it pushes messages to playerDirector, and playerDirector processes the messages and sends feedback to other players.
  • Open some interfaces in the playerDirector to receive messages. Each player can directly call this interface to send messages to the playerDirector. The player only needs to pass one parameter to the playerDirector. The purpose of this parameter is to enable playerDirector to identify the sender. Similarly, the playerDirector receives the message and reports the processing results back to the other players.

The following methods are used to develop interfaces:

var playerDirector = ( function () {
    var players = {}, // Save all players
        operations = {}; // Actions that the mediator can perform

    // Add a new player
    operations.addPlayer = function ( player ) {
        var teamColor = player.teanmColor;
        players[ teamColor ] = players[ teamColor ] || [];
        players[ teamColor ].push( player );
    }

    // Remove a player
    operations.removePlayer = function ( player ) {
        var teamColor = player.teamColor,
            teamPlayers = players[ teamColor ] || []
        for ( var i = teamPlayers.length - 1; i >= 0; i-- ){   // Iterate over the delete
            if ( teamPlayers[ i ] === player ){
                teamPlayers.splice( i, 1); }}}// Players change teams
    operations.changeTeam = function ( player, newTeamColor ) {
        operations.removePlayer( player );
        player.teamColor = newTeamColor;
        operations.addPlayer( player );
    }

    // The player dies
    operations.playerDead = function( player ){     // The player dies
        var teamColor = player.teamColor,
            teamPlayers = players[ teamColor ];   // The player's team

        var all_dead = true;

        for ( var i = 0, player; player = teamPlayers[ i++ ]; ) {if( player.state ! = ='dead' ){
                all_dead = false;
                break; }}if ( all_dead === true) {// All dead

            for ( var i = 0, player; player = teamPlayers[ i++ ]; ) { player.lose();// All players lose
            }

            for ( var color in players ){
                if( color ! == teamColor ){var teamPlayers = players[ color ];   // Players from other teams
                    for ( var i = 0, player; player = teamPlayers[ i++ ]; ) { player.win();// All players on other teams win}}}}};// Develop the interface
    var ReceiveMessage = function () {
        // The first argument is the type of message
        var message = Array.prototype.shift.call( arguments );
        operations[ message ].apply( this.arguments );
    }

    return {
        ReceiveMessage: ReceiveMessage
    }
})();
Copy the code

Decorator pattern

Decorator mode can dynamically add responsibilities to an object during program execution without changing the object itself.

A simple example:

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

var _a = a;

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

a();
Copy the code

Decorate functions with AOP

Function.prototype.before = function ( beforeFn ) {
    var _self = this; // Save a reference to the original function
    return function () { // Returns a "proxy" function that contains both the original and new functions
        beforeFn.apply( this.arguments ); // Execute a new function to ensure this is not hijacked

        return _self.apply( this.arguments ); // Execute the original function and return the result of the original function, ensuring this is not hijacked}}Function.prototype.after = function( afterfn ){
    var _self = this;
    return function(){
        var ret = _self.apply( this.arguments );
        afterfn.apply( this.arguments );
        returnret; }}/ / use
window.onload = function(){
    alert (1);
}

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

Copy the code

Decorator pattern and proxy pattern

The most important difference between the agent pattern and the decorator pattern is their intent and design purpose. The purpose of the proxy pattern is to provide a substitute for an ontology when direct access to that ontology is inconvenient or undesirable. The ontology defines key functionality, and the proxy provides or denies access to it, or does additional things before accessing the ontology. The purpose of decorator pattern is to dynamically add behavior to an object. In other words, the Proxy pattern emphasizes a relationship (between a Proxy and its entities) that can be expressed statically, that is, it can be determined at the outset. The decorator pattern is used when the full functionality of the object is initially uncertain. The proxy pattern usually has only one layer of proxy-ontology references, whereas the decorator pattern often forms a long decorative chain.

The state pattern

Definition: Allows an object to change its behavior when its internal state changes. The object appears to modify its class.

Let’s divide the sentence into two parts, using commas. The first part means encapsulating the state into separate classes and delegating requests to the current state object, with different behavior changes as the internal state of the object changes. The second part is that from the customer’s point of view, we use objects that behave very differently in different states. This object appears to be instantiated from different classes, but in fact it uses the effect of delegation.

A generic structure for state patterns

var Light = function(){
    // instantiate each state object
    this.offLightState = new OffLightState( this );    // Holds a reference to the state object
    this.weakLightState = new WeakLightState( this );
    this.strongLightState = new StrongLightState( this );
    this.superStrongLightState = new SuperStrongLightState( this );
    this.button = null;
};
Light.prototype.init = function () {
    var button = document.createElement( 'button' ),
        self = this;
    this.button = document.body.appendChild( button );
    this.button.innerHTML = 'switch';
    this.currState = this.offLightState; // Set the default state

    this.button.onclick = function () { self.currState.buttonWasPressed(); }}// List a state object, turn off the light state object
var OfflightState = function( light ) {
    this.light = light;
}

OfflightState.prototypr.buttonWasPressed = function () {
    console.log('weak light');
    this.light.setState( this.light.weakLightState );
}
Copy the code

State machine: Use closure implementations to delegate customer operations to state objects that need to handle the hijacking of this

var delegate = function ( client, delegation ) {
    return {
        buttonWasPressed: function () {
            return delegation.buttonWasPressed.apply( client, arguments); }}};var FSM = {
    off: {
        buttonWasPressed: function(){
            console.log( 'off' );
            this.button.innerHTML = 'Next time you press me, turn on the light.';
            this.currState = this.onState; }},on: {
        buttonWasPressed: function(){
            console.log( 'turn on the light' );
            this.button.innerHTML = 'Next time you press me, turn off the light.';
            this.currState = this.offState; }}};var Light = function () {
    this.offState = delegation( this, FSM.off );
    this.onState = delegation( this, FSM.on );
    this.currState = this.offState;
    this.button = null;
}

Light.prototype.init = function () {
    var button = document.createElement( 'button' );
    button.innerHTML = 'Lights off';
    this.button = document.body.appendChild( button );
    this.button.onclick = function () { self.currState.buttonWasPressed(); }}var light = new Light();
light.init();
Copy the code

Adapter mode

The role of the adapter pattern is to solve the problem of interface incompatibilities between two software entities. Using the adapter pattern, two software entities that would otherwise not work due to incompatible interfaces can work together.

When a component is encapsulated in a project, it is often encountered that the interface defined by the component does not correspond exactly with the data returned by the background. In this case, an apater function is a convenient choice to convert the data.