Title: JavaScript design mode and development of actual combat press: Turing community website: www.ituring.com.cn/book/1632

Basic knowledge of

Object-oriented JavaScript

JavaScript does not provide traditional object-oriented class inheritance and support for abstract classes and interfaces, but implements inheritance between objects through prototype delegation.

Programming languages include static languages and dynamic languages:

Static languages have the advantage of being able to detect type mismatch errors and specify data types at compile time, which increases compilation speed. The disadvantage is that it forces programmers to write programs under a strong contract.

The advantage of dynamic languages is that they write less code, look concise, and allow programmers to focus more on business logic. The disadvantage is that variable types cannot be guaranteed and type errors can occur at run time.

JavaScript is a dynamic language that can call any method on an object without type detection. It’s all based on duck type: if it walks like a duck and quacks like a duck, it’s a duck.

The duck model instructs us to focus on the behavior of the object rather than the object itself, i.e. on HAS-A rather than IS-A. You can implement dynamically typed languages using the Duck pattern one of the principles of “programming to the interface, not to the implementation”

Polymorphic polymorphism

The same operation applied to different objects can produce different interpretations and execution results.

The idea behind it is to separate the “what” from the “who” and “how”, to separate the “things that do not change” from the “things that can change”. Isolating the immutable, encapsulating the mutable, also meets the open-closed principle.

var makeSound = function( animal ) {
    animal.sound();
}
// call to pass in different objects
makeSound(new Duck());
makeSound(new Chicken());
Copy the code

Using inheritance to achieve polymorphic effects is the most common way to make an object appear polymorphic. Inheritance includes implementation inheritance and interface inheritance. JavaScript variable types are mutable at run time, so JavaScript object polymorphism is inherent.

encapsulation

The purpose of encapsulation is to hide information. Encapsulating includes encapsulating data, encapsulating implementation, encapsulating type and encapsulating change.

At the design pattern level, encapsulation at the most important level is encapsulation of change. Design patterns can be divided into

  • Creation pattern: Creating an object is an abstract action that encapsulates changes in the object’s behavior
  • Structural pattern: the combination of packaging structures
  • Behavioral pattern: Encapsulates changes in the behavior of objects

The prototype pattern

JavaScript is based on archetypal inheritance. The prototype pattern is not only a design pattern, but also a programming generic.

If you use prototype mode, you can do the same thing by simply calling the responsible cloning method. The key to the implementation of prototype mode is whether the clone method is provided by the language itself. ECMAScript5 provides the object. create method.

In browsers that do not support the object. create method:

Object.create = Object.create || function(obj) {
    var F = function() {};
    F.prototype = obj;
    return new F();
}
Copy the code

Clone identical objects through prototype mode. Prototype mode provides a convenient way to create objects of a certain class. Cloning is just a means to create objects.

Nature of prototype inheritance: Delegation mechanism based on prototype chain.

Making a prototype generic includes at least the following rules:

  • All data is an object
  • You get an object not by instantiating the class, but by finding an object as a prototype and cloning it
  • The object remembers its prototype
  • If an object cannot respond to a request, it delegates the request to its own prototype

The root Object in JavaScript is the Object.prototype Object. Object. Prototype is an empty Object. Every JavaScript Object is cloned from Object.prototype.

ECMAScript5 provides object. getPrototypeOf to view Object prototypes

var obj = new Object(a);console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
Copy the code

JavaScript functions can be called either as normal functions or as constructors. The new operator creates the Object by cloning the Object.prototype Object and doing some additional operations.

New operation process:

var objectFactory = function() {
    var obj = new Object(a);// Clone an empty object
    Constructor = [].shift.call(arguments); // Get the external pass constructor
    obj.__proto__ = Constructor.prototype; // Point to the correct type
    var ret = Constructor.apply(obj, arguments); // Use an external pass constructor to set properties for obj
    return typeof ret === 'object' ? ret : obj; // Make sure the constructor always returns an object.
}
// Use the function
function A(name) {this.name = name; }var a = objectFactory(A, 'tom');
Copy the code

JavaScript provides an object with a hidden attribute named __proto__. By default, the __proto__ attribute of an object points to the prototype object of its Constructor, {Constructor}. Prototype. Chrome and Firefox, among others, expose the __proto__ attribute of an object.

var a = new Object(a);console.log(a.__proto__ === Object.prototype); // true
Copy the code

With current JavaScript engines, creating objects through object.create is inefficient and usually slower than creating objects using constructors.

This, Call, and apply

JavaScript’s this always refers to an object, which object is dynamically bound at runtime based on the execution environment of the function, not the environment in which the function was declared.

In addition to the infrequent with and eval cases, there are four general situations that this refers to in practical application:

  1. Method invocation as an object: When a function is called as an object method, this refers to that object

    var obj = {
        a: 1.getA: function() {
            alert(this === obj); // true
            alert(this.a); // Output: 1
        }
    }
    obj.getA();
    Copy the code
  2. Called as a normal function: When the function is not called as an object, this always refers to the global object. This global object is the Window object.

    window.name = 'globalName';
    var getName = function() {
        return this.name;
    }
    console.log(getName()); // globalName
    Copy the code

    ECMAScript5 In strict mode, this refers to undefined

  3. Constructor call: When the new operator calls a function, the function always returns an object to which this refers

    var MyClass = function() {
        this.name = 'sven';
    }
    var obj = new MyClass();
    alert(obj.name); // sven;
    Copy the code

    Note that if the constructor explicitly returns an object of type object, the result of this operation will return that object instead of this:

    var MyClass = function() {
        this.name = 'sven';
        return { // Explicitly returns an object
            name: 'name'}}var obj = new MyClass();
    alert(obj.name); // name;
    Copy the code
  4. Function.prototype.call or function.prototype. apply: can dynamically change this passed in, a common Function of functional programming

    var obj1 = {
        name: 'sven'.getName: function() {
            return this.name; }};var obj2 = {
        name: 'name'
    };
    console.log(obj1.getName()); // sven
    console.log(obj1.getName.call(obj2)); // name
    Copy the code

The loss of this

Instead of the document.getElementById function, which is too long, use:

var getId = function(id) {
    return document.getElementById(id);
}
getId('div1');
Copy the code

When executed, an exception is thrown because many engines require this in their internal implementations of the document.getelementById method. When referenced by getId, this refers to the window instead of the original document, which can be corrected by Apply

document.getElementById = (function(func) {
    return function() {
        return func.apply(document.arguments);
    }
})(document.getElementById);
var getId = document.getElementById;
getId('div1');
Copy the code

Call and Apply usage

Call and Apply are both very common methods. The function is exactly the same, except for the form of the argument passed in.

Apply takes two arguments. The first argument points to the this object in the function body, and the second argument is a collection with a subscript. This collection can be an array or an array of classes.

Call takes a variable number of arguments. Like apply, the first argument represents the “this” in the function, and each argument is passed to the function from the second argument.

Practical use:

  1. Change the direction of this
  2. Function.prototype.bind: Specifies this inside the function

Simplified version of the bind

Function.prototype.bind = function(context) {
    var self = this; // keep the original function
    return function() {
        // Return a new function
        return self.apply(context, arguments);
        // When a new function is executed, the previously passed context is treated as this inside the new function}}Copy the code

Optimized version of BIND: You can pre-fill in some parameters

Function.prototype.bind = function() {
    var self = this; // keep the original function
    // This context to bind
    var context = [].shift.call(arguments);
    // The remaining parameters are converted to arrays
    var args = [].slice.call(arguments);
    return function() {
        // Return a new function
        return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
        // When a new function is executed, the previously passed context is treated as this inside the new function
        // Combine the arguments passed twice as arguments to the new function}}var obj = {name: 'sven'};
var func = function(a, b, c, d) {
    alert(this.name); // sven
    alert([a, b, c, d]); // [1, 2, 3, 4]
}.bind(obj, 1.2);
func(3.4);
Copy the code
  1. Methods that borrow other objects borrow constructors to achieve an inheritance-like effect
var A = function(name) {this.name = name; }var B = function() {A.apply(this.arguments); }var b = new B('sven');
Copy the code

Function argument list arguments is an array-like object. Although it has subscripts, it is not really an Array, so you can’t use functions related to arrays. This is often the case with the array. prototype object.

(function(){
    Array.prototype.push.call(arguments.3);
    console.log(arguments); / / [1, 2, 3]}) (1.2);
Copy the code

V8 Array. Prototype. push implementation

function ArrayPush() {
    var n = TO_UINT32( this.length ); // The length of the pushed object
    var m = %_ArgumentsLength(); // Number of push arguments
    for(var i=0; i<m; i++) {
        this[i+n] = %_ArgumentsLength(i); // Copy the element
    }
    this.length = n + m; // Modify the length attribute
    return this.length;
}
Copy the code

Array.prototype.push is actually a property copy process, adding parameters subscript to the pushed object and changing the length property of the object. By inference, we can pass any object to array.prototype.push

var a = {};
Array.prototype.push.call(a, 'first');
alert(a.length); / / 1
alert(a[0]); // first
Copy the code

Array.prototype.push must satisfy two conditions

  • The object itself can access attributes; passing in the number type has no effect
  • The length attribute of the object is readable and write. an error is reported when a function calls length

Closures and higher-order functions

The formation of closures is closely related to the scope and lifetime of variables.

Variable scope

Variable scope refers to the valid range of a variable.

When a variable is declared without var, it becomes a global variable, causing naming conflicts. Use the var keyword to declare a variable in a function. In this case, the variable is local and can only be accessed inside the function.

In JavaScript, functions can be used to create function scopes. When a variable is searched, it is searched layer by layer along the chain of scopes created by the code execution environment until it reaches the global object.

The lifetime of a variable

For global variables, the lifetime is permanent unless we actively destroy the global variable. Variables declared by var inside a function lose value when exiting the function and are destroyed when the function ends.

var func  = function() {
    var a = 1;
    return function() {
        a++;
        console.log(a); }}var f = func();
f(); / / 2
f(); / / 3
Copy the code

Var f = func() returns a reference to an anonymous function that asks questions about the context in which func() was called, and in which the local variable A has always lived. Since the local variable can still be accessed, it will not be destroyed. This generates a closure structure.

Application of closures

  1. Encapsulate variables: Encapsulate “private” variables that do not need to be exposed globally.
  2. Perpetuating the life of local variables: Sending requests may lose data in some older browser implementations. Each request may not succeed in sending an HTTP request because local variables can be destroyed at any time before the request is sent, causing the request to be lost. Closure can be used to solve the problem:
var report = (function() {
    var imgs = [];
    return function(src) {
        var img = newImage(); imgs.push(img); img.src = src; }})Copy the code
  1. Implement a complete object-oriented system using closures
  2. Closures implement the command pattern: The intent of the command pattern is to encapsulate the request as an object to decouple the coupling between the originator and receiver of the request. The receiver can be pre-planted into command mode before the command is executed. Closures do this by enclosing the command receiver in the context of the closure.
var TV = {
    open: function() {
        consoel.log('Turn on the TV');
    },
    close: function() {
        console.log('Turn off the TV'); }}var createCommand = function(receiver) {
    var execute = function() {
        return receiver.open();
    }
    var undo = function() {
        return receiver.close();
    }
    return {
        execute: execute,
        undo: undo
    }
};
var setCommand = function(command) {
    document.getElementById('execute').onclick = function() {
        command.execute();
    }
    document.getElementById('undo').onclick = function() {
        command.undo();
    }
}
setCommand(createCommand(TV));
Copy the code
  1. Closures and memory management: It is easy to create circular references with closures, which can cause memory leaks if DOM nodes are stored in the closure’s scope chain. In Internet explorer, objects in BOM and DOM are realized as COM objects using C++, and the garbage collection mechanism of COM objects is reference counting strategy. In the garbage collection mechanism based on reference counting strategy, if two objects form circular references, objects may not be recycled. Memory leaks occur.

Higher-order functions

Higher-order functions satisfy at least the following conditions:

  • Functions can be passed as arguments
    1. Callback function: such as ajax asynchronous request application
    2. Array.prototype.sortThe receiver function specifies collation rules
  • The function can be output as a return value
    1. Determine the data type
    2. GetSingle singleton mode
// Type judgment
var isString = function(obj) {
    return Object.prototype.toString.call(obj) === '[object String]';
}
var isArray = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
}
var isNumber = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Number]';
}
// Use closure optimization
var isType = function(type) {
    return function(obj) {
        return Object.prototype.toString.call(obj) === '[object '+ type +'] '; }}var isString = isType('String');
var isArray = isType('Array');
var isNumber = isNumber('Number');
// getSingle
var getSingle = function(fn) {
    var ret;
    return function() {
        return ret || (ret = fn.apply(this.arguments)); }}Copy the code

Higher-order functions implement AOP

AOP is faceted programming: the main function of AOP is to extract some functions irrelevant to the core business logic module, such as log statistics, security control, exception handling, etc.

Java implements AOP through reflection and dynamic proxy mechanisms, while JavaScript “dynamically weaves” one Function into another by extending function.Prototype

Decorator mode is used:

Function.prototype.before = function(beforefn) {
    var __self = this; // Save a reference to the original function
    return function() {
        // Return a proxy function that contains both the original function and the new function
        beforefn.apply(this.arguments);
        return __self.apply(this.arguments); }}Function.prototype.after = function(afterfn) {
    var __self = this;
    return function() {
        var ret = __self.apply(this.arguments);
        afterfn.apply(this.arguments);
        returnret; }}var func = function() {console.log(2); } func = func.before(function(){ 
    console.log(1);
}).after(function(){
    console.log(3);
});
func(); / / 1 2 3
Copy the code

Other applications of higher order functions

  1. Currying: A currific, also called partial evaluation. A function that curries first takes some arguments and then returns another function, with the arguments just passed stored in the function formation closure. When the function is actually required to ask for a value, the arguments passed in before are applied once and for all.

General currying

var currying = function(fn) {
    var args = [];
    return function() {
        if(arguments.length === 0) {
            return fn.apply(this, args);
        } else {
            [].push.apply(args, arguments);
            return arguments.callee; }}}/ / case
var cost = (function() {
    var money = 0;
    return function() {
        for(var i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
        }
        returnmoney; }});var cost = currying(cost); // Convert to the curring function
cost(100);
cost(200);
cost(300);
cost(); / / 600
Copy the code
  1. Uncurrying: extracting the generalization this.

One of the implementation methods:

Function.prototype.uncurrying = function() {
    var self = this;
    return function() {
        var obj = Array.prototype.shift.call(arguments);
        return self.apply(obj, arguments); }}// Convert array push to a generic function
var push = Array.prototype.push.uncurrying();
(function(){
    push( arguments.4);
    console.log(arguments); // 1, 2, 3, 4}) (1.2.3);
Copy the code
  1. Function of the throttle: In some scenarios, functions can be called very frequently, causing major performance problems. For example,
    • window.onresizeEvent, which fires very frequently when the browser window is dragged to change size
    • mousemoveEvent, drag event
    • Upload progress. Make frequent progress notifications

The common problem with all of these is that functions fire too often.

The throttle function works by delaying the execution of a function with setTimeout. If the delayed execution is not complete, subsequent calls to the function are ignored. The throttle function takes two parameters, the first being the function to delay execution and the second being the time to delay execution

var throttle = function(fn, interval) {
    var __self = fn; // Save function references that need to be deferred
    var timer; / / timer
    var firstTime = true; // is the first call
    return function() {
        var args = arguments;
        var _me = this;
        if(firstTime) {
            // If this is the first call, there is no need to delay execution
            __self.apply(__me, args);
            return firstTime = false;
        }
        if(timer) {
            // If the timer is still running, the previous delay has not been completed
            return false;
        }
        timer = setTimeout(function(){
            clearTimout(timer);
            timer = null;
            __self.apply(__me, args);
        }, interval || 500);
    };
};
/ / case
window.onresize = throttle(function() {
    console.log(1);
}, 500);
Copy the code
  1. Time-sharing functions: Some functions are called by the user actively, but for some objective reasons, can seriously affect the performance of the page. For example, if you render a page with hundreds or thousands of nodes at a time, adding a large number of DOM nodes to the page in a short period of time will overwhelm the browser, causing the browser to stall or even fake death.

One solution is the following timeChunk function, which lets the creation of nodes be done in batches. The timeChunk function takes three arguments: the first argument is the data used to create the node, the second argument is the function that encapsulates the logic used to create the node, and the third argument is the number of nodes created in each batch.

var timeChunk = function(ary, fn, count) {
    var obj;
    var t;
    var len = ary.length;
    var start = function() {
        for(var i = 0; i <Math.min(count || 1, ary.length); i++) {
            varobj = ary.shift(); fn(obj); }};return function() {
        t = setInterval(function() {
            if(ary.length === 0) {
                // If the nodes are already created
                return clearInterval(t);
            }
            start();
        }, 200);
    };
};
Copy the code
  1. Lazy loading function: The first time we enter the conditional branch, we rewrite the function inside the function. The overwritten function is the desired function. The next time we enter the function, there is no branch statement
var addEvent = function(elem, type, handler) {
    if(window.addEventListener) {
        addEvent = function(elem, type, handler) {
            elem.addEventListener(type, handler, false); }}else if(window.attachEvent) {
        addEvent = function(elem, type, handler) {
            elem.attachEvent('on'+type, handler);
        }
    }
    addEvent(elem, type, handler);
}
Copy the code

Design patterns

It introduces 14 common design patterns in JavaScript development

The singleton pattern

The definition is to ensure that a class has only one instance and provide a global access point to access it.

var Singleton = function(name) {
    this.name = name;
    this.instacne = null;
}
Singleton.getInstance = (function() {
    var instance = null;
    return function(name) {
        if(! instance) { instance =new Singleton(name);
        }
        returninstane; }})Copy the code

The singleton pattern can be implemented in combination with the proxy pattern.

Using namespaces

Proper use of namespaces will not eliminate global variables and reduce the number of global variables

var namespace = {
    a: function () {alert(1); },b: function () {alert(2);}
}
Copy the code

Create namespaces dynamically

var MyApp = {};
MyApp.namespace = function(name) {
    var parts = name.splice('. ');
    var current = MyApp;
    for(var i in parts) {
        if(!current[parts[i]]) {
            current[parts[i]] = {};
        }
        current = current[parts[i]];
    }
}
/ / case
MyApp.namespace('dom.style');
var MyApp = {
    dom: {
        style: {}}}Copy the code

Lazy singleton pattern: Object instances are created only when needed.

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

Pass in the function that creates the object, and then let getSingle return a new function with a variable result to hold the result of fn’s calculation. The Result variable is never destroyed because it is inside the closure.

This places the responsibility for creating instance objects and the responsibility for managing singletons in two separate methods. These two methods change independently and do not affect each other. When they are linked together, they accomplish the function of creating unique instance objects. Consistent with the principle of single responsibility.

Not only for creating objects, but also for binding events.

The strategy pattern

Definition: define a class of algorithms, encapsulate them one by one, and make them interchangeable.

A policy-based program consists of at least two parts. The first part is a set of policy classes, which encapsulate the specific algorithm and are responsible for the specific calculation process. The second part is the environment class Context, which receives the client’s request, which is then delegated to a policy class. A reference to a policy object is maintained in the Context.

var strategies = {
    'S': function(salary) {
        return salary * 4;
    },
    'A': function(salary) {
        return salary * 3;
    },
    'B': function(salary) {
        return salary * 2; }};var calculateBonus = function(level, salary) {
    return strategies[level](salary);
}
/ / case
console.log(calculateBonus('S'.20000));
Copy the code

In real development, we often diffuse the meaning of the algorithm so that the policy pattern can also encapsulate a set of business rules. For example, use policy patterns for form validation.

/*************** Policy object *******************/
var strategies = {
    isNonEmpty: function(value, errorMsg) { / / not be empty
        if (value === ' ') {
            returnerrorMsg; }},minLength: function(value, length, errorMsg) { // Limit the minimum length
        if (value.length < length) {
            returnerrorMsg; }},isMobile: function(value, errorMsg) { // Mobile phone number format
        if (!/ (a ^ 1 [3 | | 5 8] [0-9] {9} $) /.test(value)) {
            returnerrorMsg; }}};/****** Define classes to hold what you want to verify **********/
var Validator = function() {
    this.cache = []; // Save the verification rule
};
Validator.prototype.add = function(dom, rule, errorMsg) {
    var ary = rule.split(':'); // Separate strategy from parameters
    this.cache.push(function() { // Wrap the verification step in an empty function and place it in the cache
        var strategy = ary.shift(); // User-selected strategy
        ary.unshift(dom.value); // Add input value to the parameter list
        ary.push(errorMsg); // Add errorMsg to the parameter list
        return strategies[strategy].apply(dom, ary);
    });
};
Validator.prototype.start = function() {
    for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
        var msg = validatorFunc(); // Start the verification and get the return information after the verification
        if (msg) { // If an exact value is returned, the verification fails
            returnmsg; }}};/********** Client call code *******************/
var validataFunc = function() {
    var validator = new Validator(); // Create a Validator
    /*************** Add some verification rules ****************/
    validator.add(registerForm.userName, 'isNonEmpty'.'User name cannot be empty');
    validator.add(registerForm.password, 'minLength:6'.'Password length must not be less than 6 characters');
    validator.add(registerForm.phoneNumber, 'isMobile'.'Incorrect format of mobile number');
    var errorMsg = validator.start(); // Get the verification result
    return errorMsg; // Return the verification result
}
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function() {
    var errorMsg = validataFunc(); // If errorMsg has an exact return value, it is not verified
    if (errorMsg) {
        alert(errorMsg);
        return false; // Block form submission}};Copy the code

Advantages of policy mode:

  1. Multiple conditional selection statements can be effectively avoided by using combination, delegate and polymorphism techniques
  2. Provides perfect support for the open-and-close principle, encapsulating algorithms in a separate strategy, making them easy to switch, easy to understand, and easy to extend.
  3. The algorithm can be reused elsewhere in the system to avoid many repetitive copy-and-paste operations
  4. Using composition and delegation to give the Context the ability to perform the algorithm is an alternative to inheritance

The proxy pattern

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

There are two proxy modes: the protection proxy rejects some requests through the proxy, which controls the access of objects with different permissions to the target object. Delay the creation of some expensive objects as virtual proxies until they are really needed.

Virtual proxy cases:

var myImage = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) { imgNode.src = src; }}}) ();// proxy, display local image first, display network image after loading
var proxyImage = (function() {
    var img = new Image;
    img.onload = function() {
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            myImage.setSrc('loading.jpg'); img.src = src; }}}) (); proxyImage.setSrc('https://p.qpic.cn/music_cover/Fe6emiag6IuVbMib3oN6yctRX8ZBICoa4liaYZkwZoSCaJdw7tOW5bCiaA/300?n=1');
Copy the code

Single responsibility principle: There should be only one reason for a class to change. Responsibility is defined as “cause of change”.

If both the proxy object and the ontology object are a function, and both functions must be executed, they can be considered to have a consistent “interface.”

var myImage = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    // return the function
    return function(src) {
        imgNode.src = src;
    }
})();
Copy the code

The virtual proxy merges HTTP requests

// The time to send the file, used to bind the event, and synchronize the file to another server when clicked:
var synchronousFile = function(id) {
    console.log('Start file synchronization with id:' + id);
};
// Delay 2 seconds, send all requests together, reduce the server load
var proxySynchronousFile = (function() {
    var cache = [], // Save the ids to be synchronized over a period of time
        timer; / / timer
    return function(id) {
        cache.push(id);
        if (timer) { // Ensure that the started timer is not overwritten
            return;
        }
        timer = setTimeout(function() {
            synchronousFile(cache.join(', ')); // Send the set of ids to be synchronized to the ontology after 2 seconds
            clearTimeout(timer); // Empty the timer
            timer = null;
            cache.length = 0; // Clear the ID collection
        }, 2000);
    }
})();
Copy the code

The caching proxy

The cache proxy can provide temporary storage for some expensive operation results, and can directly return the stored operation results on the next operation if the parameters passed in are the same as the previous one.

/**************** computes the product *****************/
var mult = function(){
    var a = 1;
    for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a * arguments[i];
    }
    return a;
};
/**************** calculates plus and *****************/
var plus = function(){
    var a = 0;
    for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a + arguments[i];
    }
    return a;
};
/********* Create a factory for caching proxies *************/
var createProxyFactory = function( fn ){
    // Create a cache object
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments.', ' );
        if ( args in cache ){
            return cache[ args ];
        }
        return cache[ args ] = fn.apply( this.arguments); }};/ / case
var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
console.log( proxyMult( 1.2.3.4));// Output: 24
console.log( proxyPlus( 1.2.3.4));// Output: 10
Copy the code

Other proxy modes: firewall proxy, remote proxy, protected proxy, intelligent reference proxy, etc

Iterator pattern

The iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing the internal representation of the object. Such as array.prototype.foreach in JavaScript

Inner iterator: Takes two arguments, the first being the array to be looped through and the second being the callback function to be fired after each step in the loop. The internal iterator is convenient to call, and the outside world does not care about the internal implementation. The interaction with the iterator is only the first initial call, and the defect is that the internal iteration rules cannot be modified.

var each = function(ary, callback) {
    for(var i = 0, l = ary.length; i < l; i++) {
        callback.call(ary[i], i, ary[i]); // Pass the subscript and element as arguments to the callback function}}Copy the code

External iterators: Must display the request to iterate over the next element. Increased call complexity, but also relatively increased flexibility.

// More widely applicable to meet more changeable needs
var Iterator = function( obj ){
    var current = 0;
    var next = function(){
        current += 1;
    };
    var isDone = function(){
        return current >= obj.length;
    };
    var getCurrItem = function(){
        return obj[ current ];
    };
    return {
        next: next,
        isDone: isDone,
        getCurrItem: getCurrItem
    }
};
// Compare functions
var compare = function( iterator1, iterator2 ){
    while(! iterator1.isDone() && ! iterator2.isDone() ){if( iterator1.getCurrItem() ! == iterator2.getCurrItem() ){throw new Error ( Iterator1 and iterator2 are not equal );
        }
        iterator1.next();
        iterator2.next();
    }
    console.log( Iterator1 is equal to iterator2 );
}
var iterator1 = Iterator( [ 1.2.3]);var iterator2 = Iterator( [ 1.2.3]); compare( iterator1, iterator2 );Iterator1 is equal to iterator2
Copy the code

The iterator pattern iterates not only over arrays, but also over some array-like objects. As long as the aggregate object being iterated has the length attribute and can be accessed with subscript, it can be iterated.

// reverse iterator
var reverseEach = function(ary, callback) {
    for(var l = ary.length- 1; l >= 0; l--) { callback(l, ary[l]); }}// Terminate the iterator
var each = function( ary, callback ){
    for ( var i = 0, l = ary.length; i < l; i++ ){
        if ( callback( i, ary[ i ] ) === false) {// Callback returns false, prematurely terminating the iteration
            break; }}};Copy the code

Publish and subscribe

The publisk-subscribe pattern, also known as the observer pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified.

The publish-subscribe pattern is widely used in asynchronous programming as an alternative to passing callbacks. The publish-subscribe pattern replaces hard-coded notification between objects so that one object no longer explicitly calls an interface of another object.

Binding event functions to DOM nodes applies the publish-subscribe pattern.

Common publish-subscribe code:

var event = {
    clientList: [].// Two dimensional data, used to store subscription events
    listen: function( key, fn ){ // Subscribe to events
        if(!this.clientList[ key ] ){
            this.clientList[ key ] = [];
        };
        this.clientList[ key ].push( fn ); // Subscribed messages are added to the cache list
    },
    trigger: function(){ // Publish events
        var key = Array.prototype.shift.call( arguments ), // Get the event key
        fns = this.clientList[ key ];
        if ( !fns || fns.length === 0) {// If no corresponding message is bound
            return false;
        }
        for( var i = 0, fn; fn = fns[ i++ ]; ) { fn.apply(this.arguments ); // arguments are arguments to take with trigger}},remove: function( key, fn ){ // Remove the subscription
		var fns = this.clientList[ key ];
		if ( !fns ){ // If the message corresponding to the key is not subscribed, it is returned directly
			return false;
		}
		if ( !fn ){ // If no specific callback function is passed in, all subscriptions for the message corresponding to the key need to be unsubscribed
			fns && ( fns.length = 0 );
		}else{
			for ( var l = fns.length - 1; l >=0; l-- ){ // Reverse traverse the list of subscribed callback functions
				var _fn = fns[ l ];
				if ( _fn === fn ){
					fns.splice( l, 1 ); // Delete subscriber callback function}}}}};// You can install publish-subscribe for all objects
var installEvent = function( obj ){
    for ( var i inevent ){ obj[ i ] = event[ i ]; }};Copy the code

Intermodule communication

With the publish-and-subscribe model, you can communicate between two well-packaged modules that are completely unaware of each other’s existence.

// Universal publish-subscribe mode
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
    }
})();
/ / module a
var a = (function() {
    var count = 0;
    var button = document.getElementById('count');
    button.onclick = function() {
        Event.trigger('add', count++);
    }
})();
B / / modules
var b = (function() {
    var div = document.getElementById('show');
    Event.listen('add'.function(count) { div.innerHTML = count; }); }) ();Copy the code

In some cases, you can save a published message and re-send it to the subscriber when an object subscribes to it. For example, offline messages in QQ.

Use namespaces to resolve naming conflicts while adding the ability to save messages

// Update events so that published content can be stored before subscribing
var Event = (function(){
    var global = this,
    Event,
    _default = 'default';
    Event = function(){
        var _listen,
        _trigger,
        _remove,
        _slice = Array.prototype.slice, // Bind the native Array function
        _shift = Array.prototype.shift, // Bind the native Array function
        _unshift = Array.prototype.unshift, // Bind the native Array function
        namespaceCache = {},
        _create,
        find,
        each = function( ary, fn ){  / / the iterator
            var ret;
            for ( var i = 0, l = ary.length; i < l; i++ ){
                var n = ary[i];
                ret = fn.call( n, i, n);
            }
            return ret;
        };
        _listen = function( key, fn, cache ){   // Add a listener
            if ( !cache[ key ] ){
                cache[ key ] = [];
            }
            cache[key].push( fn );
        };
        _remove = function( key, cache ,fn){ // Remove the listener
            if ( cache[ key ] ){
                if( fn ){
                    for( var i = cache[ key ].length; i >= 0; i-- ){
                        if( cache[ key ] === fn ){
                            cache[ key ].splice( i, 1); }}}else{ cache[ key ] = []; }}}; _trigger =function(){ // Trigger the event
            var cache = _shift.call(arguments),
            key = _shift.call(arguments),
            args = arguments,
            _self = this,
            ret,
            stack = cache[ key ];
            if(! stack || ! stack.length ){return;
            }
            return each( stack, function(){
                return this.apply( _self, args );
            });
        };
        _create = function( namespace ){ // Create a namespace
            var namespace = namespace || _default;
            var cache = {},
            offlineStack = [], // Offline event
            ret = {
                listen: function( key, fn, last ){
                    _listen( key, fn, cache );
                    if ( offlineStack === null) {return;
                    }
                    if ( last === 'last' ){
                    }else{
                        each( offlineStack, function(){
                            this(a); }); } offlineStack =null;
                },
                one: function( key, fn, last ){
                    _remove( key, cache );
                    this.listen( key, fn ,last );
                },
                remove: function( key, fn ){
                    _remove( key, cache ,fn);
                },
                trigger: function(){
                    var fn,
                    args,
                    _self = this;
                    _unshift.call( arguments, cache );
                    args = arguments;
                    fn = function(){
                        return _trigger.apply( _self, args );
                    };
                    if ( offlineStack ){
                        return offlineStack.push( fn );
                    }
                    returnfn(); }};return namespace ?
            ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] :
                namespaceCache[ namespace ] = ret )
            : ret;
        };
        return {
            create: _create,
            one: function( key,fn, last ){
                var event = this.create( );
                event.one( key,fn,last );
            },
            remove: function( key,fn ){
                var event = this.create( );
                event.remove( key,fn );
            },
            listen: function( key, fn, last ){
                var event = this.create( );
                event.listen( key, fn, last );
            },
            trigger: function(){
                var event = this.create( );
                event.trigger.apply( this.arguments );
            }
        };
    }();
    returnEvent; }) ();/************* Publish and subscribe to ***************/ 
Event.trigger('click'.1);
Event.listen('click'.function(a) {
    console.log(a);     
});


/********** uses the namespace ******************/ 
Event.create('namespace1').listen('click'.function(a) {
    console.log(a);
})

Event.create('namespace1').trigger('click'.1);

Event.create('namespace2').listen('click'.function(a) {
    console.log(a);
})
Event.create('namespace2').trigger('click'.2);
Copy the code

JavaScript doesn’t have to choose whether to use a push or pull model. The push model refers to the publisher pushing all changed state and data to subscribers at once when an event occurs. The pull model is one in which the publisher merely notifies the subscriber that an event has occurred, and the publisher provides some public interface for the subscriber to actively pull data.

The publish-subscribe pattern has the advantages of temporal decoupling and inter-object decoupling. It’s very widely used. The disadvantage is that creating a subscriber itself consumes a certain amount of time and memory, weakening the connection between objects.

Command mode

Command mode is one of the simplest and most elegant modes. A command in command mode refers to an instruction that does something specific.

The most common scenario is when you need to send a request to some object without knowing who the recipient of the request is or what action is being requested. A loose-coupling approach is needed to design the program so that the sender and receiver of the request decouple from each other.

The origin of the command pattern is an object-oriented substitute for the callback function.

var button1 = document.getElementById('button1');
// Set commands that accept buttons and bound functions
var setCommand = function(button, command) {
    button.onclick = function() { command.execute(); }};var MenuBar = {
    refresh: function() {
        alert('Refresh menu interface'); }};// Set the command to provide the execute function externally
var RefreshMenuBarCommand = function(receiver) {
    return {
        execute: function() { receiver.refresh(); }}};var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
Copy the code

macros

A macro command is a set of commands. You can execute a batch of commands at a time by executing macro commands.

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'); }};var MacroCommand = function(){
    return {
        commandsList: [].add: function( command ){
            this.commandsList.push( command );
        },
        execute: function(){
            for ( var i = 0, command; command = this.commandsList[ i++ ]; ) { command.execute(); }}}};var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openPcCommand );
macroCommand.add( openQQCommand );
macroCommand.execute();
Copy the code

Dumb commands: In general, the command pattern keeps a receiver in the command object responsible for actually executing the client’s request and only for forwarding the client’s request to the receiver for execution. In this pattern, the request initiator and the request receiver are as decoupled as possible.

Smart commands: Command objects that fulfill requests directly, eliminating the need for the recipient’s presence, are also called smart commands. Intelligent commands without receivers are similar to policy patterns and can only be distinguished by intent. Policy mode All policy objects are consistent and are different means to achieve a certain goal. The intelligent command mode is broader, and the object solution target is divergent. The command mode can also complete functions such as undo and queuing.

Portfolio model

Composition pattern: Use small children to build larger objects, which themselves may be made up of smaller “grandchildren”.

In macro commands, macroCommand is called a composite object, and closeDoorCommand and openPcCommand are leaf objects.

The composite pattern forms objects into a tree structure to represent a parties-whole hierarchy. In addition to being used to represent tree structures, another benefit of the composite pattern is that it enables consistent use of individual and composite objects through the polymorphic representation of objects.

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.

In composite mode, requests are always passed through the tree following a logic: requests are passed down from the topmost object of the tree, processed as they encounter leaf objects, and traversed through the subordinate child nodes as they encounter composite objects.

var MacroCommand = function() {
    return {
        commandsList: [].add: function(command) {
            this.commandsList.push(command);
        },
        execute: function() {
            for (var i = 0, command; command = this.commandsList[i++];) { command.execute(); }}}};var openAcCommand = {
    execute: function() {
        console.log('Turn on the air conditioner'); }};/ * * * * * * * * * * home TV and acoustics are connected together, so you can use a macro command to turn on the TV and open acoustics order * * * * * * * * * /
var openTvCommand = {
    execute: function() {
        console.log('Turn on the TV'); }};var openSoundCommand = {
    execute: function() {
        console.log('Turn on the stereo'); }};var macroCommand1 = MacroCommand();
macroCommand1.add(openTvCommand);
macroCommand1.add(openSoundCommand);
/********* now combine all commands into a single "supercommand" **********/
var macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
/********* Finally bind "super command" **********/ to the remote control
var setCommand = (function(command) {
    document.getElementById('button').onclick = function() {
        command.execute();
    }
})(macroCommand);
Copy the code

The biggest advantage of the composite pattern is that you can treat composite objects and base objects identically. Customers do not need to know whether they are dealing with macro commands or common commands. As long as a command has the execute method, it can be added to the tree.

Security issues

Composite objects can have child nodes. There are no children under the leaf object, so trying to add children to the leaf object is futile. The solution is to add a throw handler:

var leafCommand = {
    / / child nodes
    execute: function() {
        console.log('Child node performs operation');
    },
    add: function() {
        throw new Error('Leaf object cannot add byte points'); }}Copy the code

The composite mode can be used for file scanning, and the file structure is tree.

Pay attention to place

  1. The combination pattern is not A parent-child relationship, but A HAS-A(aggregation) relationship
  2. Consistency of operation on leaf objects: not suitable for individual cases
  3. Bidirectional mapping: If both parent nodes contain the same child node, this composite situation requires bidirectional mapping between the parent node and the child node, but leads to complex reference relationships, which can be managed by introducing the mediator pattern
  4. Use responsibility chain mode to improve the performance of the combined mode: In the combined mode, if the tree structure is complex and the number of nodes is large, the performance is not ideal in the traversal of the tree. In this case, the responsibility chain mode can be used to avoid traversing the whole tree.

In the composite pattern, there is actually a natural chain of responsibilities between parent and child objects. The request is passed along the chain from parent object to child object, or vice versa, until an object can handle the request is encountered. This is one of the classic chain-of-duty scenarios.

You can add the parent attribute to the child node to record the index of the parent node, and update the parent attribute of the child node when you perform the add operation.

Applicable scenario

  1. Represents a partial-whole hierarchy of objects
  2. The customer wants to treat all objects in the tree uniformly

Template method pattern

The template method is a simple pattern that can be implemented using inheritance. It consists of two parts, the first part is the abstract parent class, the second part is the concrete implementation child class.

A hook method is a means of isolating change by placing hooks in easily changeable parts of a parent class. Hooks can have a default implementation, and it is up to subclasses to decide whether to use the hook.

Examples not using prototype inheritance:

// Template method
var Beverage = function( param ){
    var boilWater = function(){
        console.log( 'Boil the water' );
    };
    var brew = param.brew || function(){
        throw new Error( 'Must pass brew method' );
    };
    var pourInCup = param.pourInCup || function(){
        throw new Error( 'pourInCup method must be passed' );
    };
    var addCondiments = param.addCondiments || function(){
        throw new Error( 'Must pass the addCondiments method' );
    };
    var customerWantsCondiments = param.customerWantsCondiments || function() {
        return true; // Seasoning is required by default
    };
    var F = function(){};
    F.prototype.init = function(){
        boilWater();
        brew();
        pourInCup();
        if (this.customerWantsCondiments()) { 
            // If the hook returns true, seasoning is required
            this.addCondiments(); }};return F;
};
var Coffee = Beverage({
    brew: function(){
        console.log( 'Brew coffee in boiling water' );
    },
    pourInCup: function(){
        console.log( 'Pour the coffee into the cup' );
    },
    addCondiments: function(){
        console.log( 'With sugar and milk'); }});var Tea = Beverage({
    brew: function(){
        console.log( 'Soak tea leaves in boiling water' );
    },
    pourInCup: function(){
        console.log( 'Pour the tea into the cup' );
    },
    addCondiments: function(){
        console.log( 'Add lemon'); }});var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();
Copy the code

Hollywood principle

The Hollywood principle is that we allow components to hook themselves into higher-level components, and the higher-level components decide when and how to use those lower-level components. The template method pattern is a typical use of the Hollywood principle. In addition, the Hollywood principle is often applied to publish-subscribe patterns and callback functions.

The flyweight pattern

Sharing pattern is a pattern for performance optimization, the core is to use sharing technology to effectively support a large number of fine-grained objects. This method is applicable to scenarios where a large number of similar objects are created to reduce memory usage.

Sharing mode requires that the attributes of the object be divided into internal states and external states (states usually refer to attributes). The goal is to minimize the number of shared objects.

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

File Uploading Cases

// Strip the external state
var Upload = function(uploadType) {
    this.uploadType = uploadType;// Upload mode is an internal state
};
// Cancel the file upload
Upload.prototype.delFile = function(id) {
    // Assembles the external state of the object corresponding to the current ID into the shared object
    // Set the correct fileSize for the shared object
    uploadManager.setExternalState(id, this); 
    if (this.fileSize < 3000) {
        return this.dom.parentNode.removeChild(this.dom);
    }

    if (window.confirm('Are you sure you want to delete this file? ' + this.fileName)) {
        return this.dom.parentNode.removeChild(this.dom); }}// The factory instantiates the object to hold different internal states
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 uploadManager = (function() {
    var uploadDatabase = {}; // Save the external state of all objects
    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]; }}}}) ();var id = 0;
window.startUpload = function(uploadType, files) {
    for (var i = 0, file; file = files[i++];) {
        varuploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize); }};// Create an upload object
// Only two objects
startUpload('plugin'[{fileName: '1.txt'.fileSize: 1000
}, {
    fileName: '2.html'.fileSize: 3000
}, {
    fileName: '3.txt'.fileSize: 5000
}]);
startUpload('flash'[{fileName: '4.txt'.fileSize: 1000
}, {
    fileName: '5.html'.fileSize: 3000
}, {
    fileName: '6.txt'.fileSize: 5000
}]);
Copy the code

applicability

The need to maintain an additional Factory object and a manager object, respectively, introduces some complexity. The benefits of the share model depend largely on how and when it is used. Usage scenarios

  • A program uses a large number of similar objects
  • Because of the large number of objects, there is a large memory overhead
  • Most of an object’s state can be changed to an external state
  • Once you strip away the external state of an object, you can replace a large number of objects with relatively few shared objects.

Object pooling

Object pools maintain a pool of free objects, and if objects are needed, instead of new, they are fetched from the pool. If there are no free objects in the object pool, create a new object. HTTP connection pools and database connection pools are commonly used

Object pooling is similar to the idea of the share pattern
// But there is no separation of internal and external states
var objectPoolFactory = function(createObjFn) {
    var objectPool = [];
    return {
        create: function() {
            // Check whether the object pool is empty
            var obj = objectPool.length === 0 ?
                createObjFn.apply(this.arguments) : objectPool.shift();
            return obj;
        },
        recover: function(obj) {
            objectPool.push(obj);// Object pool reclaims dom}}};var iframeFactory = objectPoolFactory(function() {
    var iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    iframe.onload = function() {
        iframe.onload = null; // Prevent iframe reloading bug
        iframeFactory.recover(iframe); // Recycle the iframe after it is loaded
    }
    return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = 'http://baidu.com';
var iframe2 = iframeFactory.create();
iframe2.src = 'http://QQ.com';
setTimeout(function() {
    var iframe3 = iframeFactory.create();
    iframe3.src = 'http://163.com';
}, 3000);
Copy the code

Object pooling is another performance optimization that is somewhat similar to the share pattern, but without the process of separating internal and external state.

Chain of Responsibility model

Definition: Avoiding coupling between the sender and receiver of the request by giving multiple objects the opportunity to process it, connecting the objects into a chain and passing the request along the chain until one object processes it.

// Responsibility chain node function
var order500 = function( orderType, pay, stock ){
    if ( orderType === 1 && pay === true) {console.log( '500 yuan deposit, get 100 coupons' );
    }else{
        return 'nextSuccessor'; // I don't know who the next node is, just pass the request to the next node}};var order200 = function( orderType, pay, stock ){
    if ( orderType === 2 && pay === true) {console.log( '200 yuan deposit, get 50 coupons' );
    }else{
        return 'nextSuccessor'; // I don't know who the next node is, just pass the request to the next node}};var orderNormal = function( orderType, pay, stock ){
    if ( stock > 0) {console.log( 'Regular purchase, no coupons' );
    }else{
        console.log( 'Lack of mobile phone stock'); }};// Wrap the responsibility chain node of the function
var Chain = function( fn ){
    this.fn = fn; 
    this.successor = null;
};
// Specify the next node in the chain
Chain.prototype.setNextSuccessor = function( successor ){
    return this.successor = successor;
};
// Pass the request to a node
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;
};
// Asynchronous responsibility chain, manually pass requests to the next node in the responsibility chain
Chain.prototype.next = function() {
    return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}

/ / case:
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
// Set the next node
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
chainOrder500.passRequest( 1.true.500 ); // Output: 500 yuan deposit pre-order, get 100 coupons
chainOrder500.passRequest( 2.true.500 ); // Output: 200 yuan deposit pre-order, get 50 coupons
Copy the code

The greatest advantage of the chain of responsibility pattern is that it decouples the complex relationship between the request sender and N recipients. In addition, you can specify the starting node manually, and the request does not have to start from the first node in the chain.

The drawback is that there is no guarantee that a request will be processed by the nodes in the chain, so you can add a backstop receiver at the end of the chain to handle the request. For performance reasons, avoid long chains of responsibility.

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; }};var order = order500.after( order200 ).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
Copy the code

Implementing chains of responsibility with AOP is simple and clever, while simultaneously overlaying the scope of functions, which can still affect performance if the chain is too long.

The mediator pattern

The role of the mediator pattern is to decouple objects from each other. When you add a mediator object, all related objects communicate through the mediator object rather than referring to each other, notifying the mediator object when the object changes.

The mediator object makes a netted many-to-many relationship into a one-to-many relationship. There are two ways to implement a mediator object:

  1. In the publish-subscribe model, the mediator object is the subscriber and each object is the publisher, sending a message to the mediator object whenever the object state changes.
  2. Expose the receiving message interface to send messages to the mediator object by passing parameters to the interface.

The Intermediary model applies to games:

// Define the player class
function Player(name, teamColor){
    this.name = name; // Character name
    this.teamColor = teamColor; // Team color
    this.state = 'alive'; // Player state
};
/ / win
Player.prototype.win = function(){
    console.log(this.name + ' won ');
};
/ / fail
Player.prototype.lose = function(){
    console.log(this.name +' lost');
};
/ / death
Player.prototype.die = function(){
    this.state = 'dead';
    playerDirector.reciveMessage('playerDead'.this); // Send a message to the broker, the player dies
};
/ / remove
Player.prototype.remove = function(){
    playerDirector.reciveMessage('removePlayer'.this); // Send a message to the broker to remove a player
};
// Players change teams
Player.prototype.changeTeam = function(color){
    playerDirector.reciveMessage('changeTeam'.this, color); // Send a message to the broker and the player changes teams
};
/*************** Spawn the player's factory ***************/
var playerFactory = function(name, teamColor) {
    var newPlayer  = new Player(name, teamColor);
    playerDirector.reciveMessage('addPlayer', newPlayer);
    return newPlayer;
}
/*************** intermediary object ******************/
var playerDirector= (function(){
    var players = {}; // Save all players
    var operations = {}; // Actions that the mediator can perform
    /******** add a new player ****************/
    operations.addPlayer = function(player){
        var teamColor = player.teamColor; // The player's team color
        players[teamColor] = players[teamColor] || []; // If the player of this color has not already formed a team, then
        players[teamColor].push(player); // Add players to the team
    };
    /******** Remove a player ******************/
    operations.removePlayer = function(player){
        var teamColor = player.teamColor, // The player's team color
        teamPlayers = players[teamColor] || []; // All members of the team
        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){ // Players change teams
        operations.removePlayer(player); // Delete from the original team
        player.teamColor = newTeamColor; // Change the team color
        operations.addPlayer(player); // Add to a new team
    };
    /******** 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}}}}};/******** Exposed interface *****************/
    var reciveMessage = function(){
        var message = Array.prototype.shift.call(arguments); // arguments The first argument is the message name
        operations[message].apply(this.arguments);
    };
    return {
        reciveMessage: reciveMessage
    }
})();
/ / case
/ / the red team:
var player1 = playerFactory( 'preserved egg'.'red' ),
player2 = playerFactory( 'little girl'.'red' ),
player3 = playerFactory( 'baby'.'red' ),
player4 = playerFactory( 'jack'.'red' );
/ / team:
var player5 = playerFactory( 'dark girl of personages'.'blue' ),
player6 = playerFactory( 'onion'.'blue' ),
player7 = playerFactory( 'overweight'.'blue' ),
player8 = playerFactory( 'the pirates'.'blue' );
player1.die();
player2.die();
player3.die();
player4.die();
Copy the code

The intermediary pattern is a kind of realization that caters to Demeter’s law. Demeter’s law, also known as the least knowledge principle, states that one object should know as little as possible about another. Reduce coupling. The disadvantage of the mediator pattern is that a new mediator object is added to the system, and the mediator object is complex and large, which is difficult to maintain.

Decorator pattern

Decorator pattern: Assign responsibilities to objects dynamically. The ability to dynamically add responsibilities to objects while the program is running without changing the objects themselves.

Decorator function: To add new functionality to a function, you can add new functionality to a function without violating open-closed by saving a reference to the function

var a = function() {console.log(1); }var _a = a;
a = function() {_a(); console.log(2); } a();Copy the code

Adding new functionality to a function without modifying the original function has the following drawbacks: having to maintain an intermediate variable; Possible this hijacking problem.

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 function and the new function
        beforefn.apply( this.arguments ); // Execute the new function and ensure that this is not hijacked
        // The new function is executed before the original function
        return __self.apply( this.arguments ); // Execute the original function and return the result of the original function.
        // And make sure 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; }};Copy the code

Form validation cases:

var username = document.getElementById('username');
var password = document.getElementById('password');
var submitBtn = document.getElementById('submitBtn');
// Decorator mode
Function.prototype.before = function(beforefn) {
    var __self = this;
    return function() {
        if (beforefn.apply(this.arguments) = = =false) {
            // beforefn returns false directly, no longer executes the following function
            return;
        }
        return __self.apply(this.arguments); }}// Check the function
var validata = function() {
    if (username.value === ' ') {
        alert('User name cannot be empty');
        return false;
    }
    if (password.value === ' ') {
        alert('Password cannot be empty');
        return false; }}// Send the request
var formSubmit = function() {
    var param = {
        username: username.value,
        password: password.value
    }
    ajax('http:// xxx.com/login', param);
}
// Validate the label before sending the request
formSubmit = formSubmit.before(validata);
submitBtn.onclick = function() {
    formSubmit();
}
Copy the code

Decorator pattern and proxy pattern

The decorator pattern is similar in structure to the proxy pattern in that both patterns describe how to provide some level of introductory reference to an object, and both implementations retain a reference to another object and send requests to that object.

The most important difference lies in intent and design purpose. The purpose of the proxy pattern is to provide an alternative to an ontology when direct access to the ontology is inconvenient or undesirable. Usually there is only one layer of proxy-ontology reference, emphasizing the relationship between the agent and the ontology. The decorator pattern serves to dynamically add behavior to an object, often forming a long chain of decorators.

The state pattern

The state pattern definition allows an object to change its behavior when its internal state changes. The object appears to change its class. Among them, the key is to distinguish the internal state of things, and the change of the internal state of things often brings about the change of the behavior of things.

The state pattern encapsulates each state of a thing into a separate class, and the behavior associated with this state is encapsulated within this class.

Simple example:

// State transition a -> B -> C -> A ->...
var Astate = function(item) {
    this.item = item;
}
Astate.prototype.change = function() {
    // Change the state
    console.log('b');
    this.item.setState(this.item.Bstate); // Switch to the B state
}
var Bstate = function(item) {
    this.item = item;
}
Bstate.prototype.change = function() {
    // Change the state
    console.log('c');
    this.item.setState(this.item.Cstate); // Switch to C
}
var Cstate = function(item) {
    this.item = item;
}
Cstate.prototype.change = function() {
    // Change the state
    console.log('a');
    this.item.setState(this.item.Astate); // Switch to the B state
}
/ * * * * * * * * * * * * * * * item class * * * * * * * * * * * * * * * * /
var Item = function() {
    this.Astate = new Astate(this);
    this.Bstate = new Bstate(this);
    this.Cstate = new Cstate(this);
    this.currState = this.Astate;
}
// Triggers a state change event
Item.prototype.change = function() {
    this.currState.change();
}
// Update the status
Item.prototype.setState = function(newState) {
    this.currState = newState;
}
/ * * * * * * * * * * * * * * * case * * * * * * * * * * * * * * * * * * * /
var item = new Item();
item.change();
Copy the code

Defining an Abstract class

The state class will define some common methods to which the Context will eventually delegate requests to the state object.

/ / abstract classes
var State = function() {}
State.prototype.change = function() {
    throw new Error('Parent's change method must be overridden');
}
// Let subclasses inherit
var Astate = function(item) {this.item = item; } Astate.prototype =new State(); // Inherits the abstract parent class
Astate.prototype.change = function() {
    // Override the parent method
    console.log('a');
    this.item.setState(this.item.Bstate);
}
Copy the code

File uploader

// Upload the plugin
var plugin = (function() {
    var plugin = document.createElement('embed');
    plugin.style.display = 'none';
    plugin.type = 'application/txftn-webkit';
    plugin.sign = function() {
        console.log('Start file scan');
    }
    plugin.pause = function() {
        console.log('Suspend file upload');
    };
    plugin.uploading = function() {
        console.log('Start file upload');
    };
    plugin.del = function() {
        console.log('Delete file upload');
    }
    plugin.done = function() {
        console.log('File upload completed');
    }
    document.body.appendChild(plugin);
    returnplugin; }) ();Constructor to create an instance object for each state subclass
var Upload = function(fileName) {
    this.plugin = plugin;
    this.fileName = fileName;
    this.button1 = null;
    this.button2 = null;
    this.signState = new SignState(this); // Set the initial state to waiting
    this.uploadingState = new UploadingState(this);
    this.pauseState = new PauseState(this);
    this.doneState = new DoneState(this);
    this.errorState = new ErrorState(this);
    this.currState = this.signState; // Set the current state
};
// Initializes the uploaded DOM node and starts binding button events
Upload.prototype.init = function() {
    var that = this;
    this.dom = document.createElement('div');
    this.dom.innerHTML =
        ' File name :' + this.fileName +   ;
    document.body.appendChild(this.dom);
    this.button1 = this.dom.querySelector('[data-action="button1"]');
    this.button2 = this.dom.querySelector('[data-action="button2"]');
    this.bindEvent();
};
// The implementation of a specific button event
Upload.prototype.bindEvent = function() {
    var self = this;
    this.button1.onclick = function() {
        self.currState.clickHandler1();
    }
    this.button2.onclick = function() { self.currState.clickHandler2(); }};// 
Upload.prototype.sign = function() {
    this.plugin.sign();
    this.currState = this.signState;
};
Upload.prototype.uploading = function() {
    this.button1.innerHTML = 'Uploading, click pause';
    this.plugin.uploading();
    this.currState = this.uploadingState;
};
Upload.prototype.pause = function() {

    this.button1.innerHTML = 'Paused, click continue uploading';
    this.plugin.pause();
    this.currState = this.pauseState;
};
Upload.prototype.done = function() {
    this.button1.innerHTML = 'Upload completed';
    this.plugin.done();
    this.currState = this.doneState;
};
Upload.prototype.error = function() {
    this.button1.innerHTML = 'Upload failed';
    this.currState = this.errorState;
};
Upload.prototype.del = function() {
    this.plugin.del();
    this.dom.parentNode.removeChild(this.dom);
};
// Implement various state classes, using state factories
var StateFactory = (function() {
    var State = function() {};
    State.prototype.clickHandler1 = function() {
        throw new Error('Subclasses must override their parent's clickHandler1 method');
    }
    State.prototype.clickHandler2 = function() {
        throw new Error('Subclasses must override their parent's clickHandler2 method');
    }
    return function(param) {
        var F = function(uploadObj) {
            this.uploadObj = uploadObj;
        };
        F.prototype = new State();
        for (var i in param) {
            F.prototype[i] = param[i];
        }
        return F;
    }
})();

var SignState = StateFactory({
    clickHandler1: function() {
        console.log('Scanning, click invalid... ');
    },
    clickHandler2: function() {
        console.log('File is being uploaded, cannot be deleted'); }});var UploadingState = StateFactory({
    clickHandler1: function() {
        this.uploadObj.pause();
    },
    clickHandler2: function() {
        console.log('File is being uploaded, cannot be deleted'); }});var PauseState = StateFactory({
    clickHandler1: function() {
        this.uploadObj.uploading();
    },
    clickHandler2: function() {
        this.uploadObj.del(); }});var DoneState = StateFactory({
    clickHandler1: function() {
        console.log('File upload completed, click invalid');
    },
    clickHandler2: function() {
        this.uploadObj.del(); }});var ErrorState = StateFactory({
    clickHandler1: function() {
        console.log('File upload failed, click invalid');
    },
    clickHandler2: function() {
        this.uploadObj.del(); }});var uploadObj = new Upload('JavaScript Design Patterns and Development Practices');
uploadObj.init();
window.external.upload = function(state) {
    uploadObj[state]();
};
window.external.upload('sign');
setTimeout(function() {
    window.external.upload('uploading'); // Upload will start in 1 second
}, 1000);
setTimeout(function() {
    window.external.upload('done'); // Upload after 5 seconds
}, 5000);
Copy the code

Advantages of state mode:

  • Defines the relationship between state and behavior, encapsulating them in a class. Add new states and transitions by adding new state classes.
  • To avoid infinite Context expansion, the logic for state switching is distributed in the state class
  • Use an object instead of a string to record the current state, making the state switch at a glance.
  • The request action in the Context and the behavior encapsulated in the state class can easily change independently of each other.

The disadvantage is that many state classes are defined and are not easy to maintain.

Performance optimization points:

  1. Manage the creation and destruction of state objects:
    1. The state object is created and then destroyed only when it is needed. Suitable for large objects
    2. Create all state objects from the start and never destroy them. Usage and state changes frequently
  2. With the share pattern, each Context object shares a state object

And the policy pattern relationship

Both state and policy patterns encapsulate the algorithm or behavior of a family of classes, and they have a context, policies, or states to which the context delegates requests.

The difference is that each strategy class in the strategy mode is equal and parallel without any connection. In the state mode, the state and the behavior corresponding to the state are already encapsulated, the switch between the states is already specified, and the behavior change happens within the state.

Adapter mode

The adapter pattern resolves the problem of interface incompatibility between two software entities.

var EventA = {
    A: function() {
        console.log('Speak English'); }}var EventB = {
    B: function() {
        console.log('Speak Chinese'); }}// B adapter
var Badapter = {
    A: function() {
        returnEventB.B(); }}// Call the communication function
var communicate = function(item) {
    if(item.A instanceof Function) {
        item.A();
    }
}
communicate(A);
communicate(Badapter);
Copy the code

Adapter patterns can also be used for data format transformations. Some patterns are very similar in structure to the adapter pattern, such as the decorator pattern, the proxy pattern, and the facade pattern. Each of these patterns is a wrapper pattern, where one object wraps another. Again, what differentiates them is the intent of the pattern.

  • Decorator pattern is mainly used to solve the problem of mismatch between two existing interfaces. You can make them work together without changing existing interfaces
  • Decorator mode and proxy mode also do not change the original object interface, but the role of decorator mode is to add functionality to the object, often a regular chain of decorators; The adapter pattern wraps only once, and the proxy pattern wraps only once to control access to objects
  • The facade pattern is similar to an adapter in that it defines a new interface.

Design principles and programming skills

Design principles usually refer to the single responsibility principle, Richter substitution principle, dependency inversion principle, interface isolation principle, composite reuse principle and least knowledge principle.

Single responsibility principle

The single responsibility principle (SPR) is that an object (method) does only one thing.

  1. Proxy mode: The virtual proxy puts preloaded responsibilities into proxy objects
  2. Iterator pattern: Provides methods for aggregate access to objects
  3. Singleton mode: Only creates objects
  4. Decorator pattern: Adding responsibilities to objects dynamically is a way of separating responsibilities

When to separate Duties

  1. If two responsibilities always change at the same time as requirements change, there is no need to separate them.
  2. The axis of change in responsibilities is meaningful only if they are certain to change, and even if two responsibilities are coupled but they show no signs of change, there is no need to separate them for refactoring

Advantages and disadvantages of SPR principles:

The advantage is that the complexity of individual classes or objects is reduced and objects are broken down into smaller granularity by responsibility, which facilitates code reuse and unit testing.

The downside is that it significantly increases the complexity of writing code, and when objects are broken down into smaller granularity, it actually makes it harder for them to relate to each other.

Least knowledge principle

The Least Knowledge principle (LKP), also known as the Law of Demeter (LoD), states that a software entity should interact with as few other entities as possible.

The least knowledge principle requires that we design programs with minimal interaction between objects. If two objects do not have to be directly related to each other, then they should not be directly related to each other. A common practice is to introduce a third party object to assume the role of communication between these objects.

  1. The mediator pattern: By adding a mediation object, all related objects communicate through the mediator object instead of referring to each other.

  2. Facade: Provides a consistent interface for a set of interfaces in a subsystem. Facade defines a high-level interface that makes the subsystem easier to use.

    The cosmetic pattern serves to shield the customer from the complexity of a set of subsystems.

    var A = function() {
        a1();
        a2();
    }
    var B = function() {
        b1();
        b2();
    }
    var facade = function() {
        A();
        B();
    }
    facade();
    Copy the code

    Facade mode is easily confused with plain packaging. Both encapsulate something, but the key to the facade pattern is to define a high-level interface to encapsulate a set of “subsystems”. Appearance mode serves two main purposes:

    1. Provides a simple and convenient access point for a set of subsystems
    2. Isolate customers from complex subsystems without requiring them to understand the details of the subsystems.

Encapsulation is embodied in the least knowledge

Encapsulation largely represents the hiding of data, including the scope used to limit variables.

JavaScript variable scope specifies:

  • If a variable is declared globally, or if it is declared implicitly (without var) anywhere in the code, it is visible globally;
  • What is a variable explicit in a function (using var) that is visible in the function

Limit the visibility of a variable to the narrowest possible range; the less the variable affects other modules, the less chance the variable will be overwritten and run into conflicts.

Open – close principle

Software entities (classes, modules, functions), etc., should be extensible but not modifiable

There are two ways to extend function functions, one is to modify the original code, one is to add a new code.

The development-closed principle: when a program needs to change its functionality or add new features, it can use the method of adding code, but it is not allowed to change the program source code.

Too many conditional branching statements is a common reason for programs to violate the development-closed principle. The problem can be solved by using polymorphism, isolating the invariant parts and encapsulating the variable parts.

In addition to polymorphism, there are

  1. Placing hooks is one way to separate changes. A hook is placed where the program might change, and the result of the hook determines the program’s direction.
  2. Use the callback function

The development-closed principle in design patterns

  1. Publish and subscribe
  2. The template method pattern: Focuses on inheritance
  3. Strategic pattern: Focuses on composition and delegation
  4. The proxy pattern
  5. Chain of Responsibility model

Open-closed principle relativity: it is not easy to completely close a program. Too much abstraction can also increase program complexity. The following two points can be done first:

  1. Pick out the places where change is most likely to occur, and then construct abstractions to seal off those changes
  2. When changes inevitably occur, try to make changes that are relatively easy to make. For an open source library, it’s easier to modify the configuration files it provides than to modify its source code.

Interface and interface oriented programming

An interface is a collection of requests that an object can respond to.

The main purpose of abstract classes and interfaces is

  1. The true type of the object is hidden by upcasting to show the polymorphism of the object
  2. Some contractual behavior between classes.

JavaScript doesn’t need to move up, and the biggest role of interfaces in JavaScript is reduced to checking code for standardization.

Check the interface by duck type

Duck type is an important concept in object-oriented design of dynamically typed languages. Using the duck type idea, it is easy to implement interface – rather than implementation-oriented programming in dynamically typed languages without the help of supertypes.

Use the duck type idea to determine whether an object is an array

var isArray = function(obj) {
    return obj &&
        typeof obj === 'object' &&
        typeof obj.length === 'number' &&
        typeof obj.splice === 'function'
}
Copy the code

You can write interface-based command patterns in TypeScript

Code refactoring

There is an inherent relationship between patterns and refactoring. The purpose of a design pattern is to provide targets for many refactoring activities.

  • The benefits of refactoring a function:

    1. Avoid super-large functions
    2. Separate functions facilitate code reuse
    3. Independent functions are easier to copy
    4. Separate functions with a good name will themselves be useful for annotation
  • Merge repeated conditional fragments

  • Refining conditional branch statements into functions: Complex conditional branch statements make programs difficult to read and understand. Complex conditional statements can be distilled into a single function to more accurately express the meaning of the code, and the function name itself acts as a comment.

  • Fair use cycle

    var createXHR = function() {
        var versions = ['MSXML2.XMLHttp.6.Oddd'.'MSXML2. XMLHttp. 3.0'.'MSXML2.XMLHttp'];
        for(var i = 0, version; version = versions[i++];) {
            try{
                return new ActiveXObject(version);
            } catch(e) {
    
            }
        }
    };
    var xhr = createXHR();
    Copy the code
  • Instead of nesting conditional branches, let the function exit early

  • Pass object arguments instead of a long argument list: If there are too many arguments, the function is difficult to understand, and remember the order in which arguments are passed, you can put them all in one object for easy maintenance.

  • Minimize the number of parameters

  • Use less ternary operators

  • Rational use of chain call: Chain application structure is relatively stable, not easy to change later. If the structure is prone to change, the normal invocation is recommended

    var User = {
        id: null.name: null.setId: function(id) {
            this.id = id;
            return this;
        },
        setName: function(name) {
            this.name = name;
            return this; }};console.log(User.setId(1314).setName('sven'));
    Copy the code
  • Break up large classes: Break up large classes to make them leaner and easier to understand

  • Exit multiple loops with return