Design Pattern 0.5: JavaScript with a difference

Object-based JavaScript

Unlike many object-oriented languages, JavaScript did not originally have the concept of class, and ES6’s new class keyword is actually implemented through functions.

JavaScript variables are object-based. Why is that?

Let’s look at a few examples:

Case 1:

Here I define a number, which is a variable of type number, and we query its prototype with __proto__. As you can see, it belongs to a Number class. As you can see from the side, even a number is an object with its own methods (inherited from the base class).

Example 2:

let a = new Object(a)// a = {}
Object.defineProperties(a, { 
    'age': { value:12.writable:false }
})
a.age / / 12
a.age = 1
a.age / / 12
Copy the code

In this example, we use the base class of all classes, Object, to new a variable. There is nothing in the variable at this point, but using the defineProperties method, we can define attributes to the variable and also control whether or not we can change the value via writable. This is basically how you create and define attributes for an object.

Example 3:

let str = 'abcd'
console.log(str.charAt(1)) // b
String.prototype.charAt = (num) = > 'Your parameters are${num}`
console.log(str.charAt(1)) // Your argument is 1
Copy the code

We define a string and then use the charAt method, which is obviously ok. Next, we changed the charAt method on the prototype String class and re-str.charat (1). This time, we returned something different. This shows that a String is also an object, and that there is a prototype chain that calls the methods of the base class (String).

Use new to parse the role of stereotypes and prototype chains

Since JavaScript is object-based, to make any variable do anything, we need to make the object have properties. Where do those properties come from? The answer is prototype.

STR prototype, there are many methods, when the STR needs to call, because they do not have this attribute, go back to the prototype, so as to achieve the effect of using the String class function.

The __proto__ binding process is usually done in a new base class object.

When we execute the new method, we go through the following steps

a = new A('xx')

  1. Create a new object
  2. Add prototype to the new object__proto__(Here prototype chains also come into play) connections
  3. Performing (constructing) a functionA.call(a,'xx')Binds the this pointer to the new object
  4. Return this (if the constructor does not return a value)

Thus the prototype transfer is realized and the prototype chain is formed.

In JavaScript class

Calss was officially introduced in ES6. The functions of a class can be implemented using functions, so a class can also be called a function. We define a class using class and instantiate it using the new keyword. This means that each new class returns a new object, so that each time a class new is used, the objects produced by the new class do not affect each other.

In addition, because there are no object-oriented features in JavaScript, there are only two classes in JavaScript, base classes and derived classes. The base class of all classes is Object. When we use class A extends B {} inheritance, B is the base class and A is the derived class.

Design Pattern 1: Global variable optimization

Scene 1:

When we need to implement a function to verify that the user is the author, we can use several functions:

const checkId = function() {}
const checkName = function() {}
const checkWorksNum = function() {}
Copy the code

That doesn’t seem like a problem.

But when we implement a reader verification function, we also need to define several duplicate functions:

const checkId = function() {}
const checkName = function() {}
Copy the code

But since these two functions have been defined before, they will be confused with the above functions.

Scene 2:

Again, if we or someone else adds new functionality or changes some functionality later on. And since it’s a global variable, our previous function is affected by our later function.

The above scenarios are all examples of variable pollution, since global variables, once defined, can be adjusted anywhere, but can also be changed anywhere and affect the whole world, which can be described as a double-edged sword. So, what should we do about the above situation?

For scenario 1, we can use a function wrapped around an object:

const checkAutor = {
	checkId: function() {},
	checkName: function() {},
    checkWorksNum: function() {}}...const checkReader = {
    checkId: function() {},
	checkName: function() {}}Copy the code

This way, we can later call the author check method and use it by calling the corresponding object method:

checkAutor.checkId()
Copy the code

For scenario 2, we can use classes that make each call to the object a new one, so that it doesn’t interfere with each other.

class CheckAuthor {
    checkId = function() {}
	checkName = function() {}
    checkWorksNum = function() {}}let a = new CheckAuthor()
Copy the code

This allows us to create a new object every time we call it, instead of using the same object.

Design Pattern 2: JS singleton pattern

(14) JS design mode (I)- singleton mode – SegmentFault

Advantages and Disadvantages of singleton mode and usage Scenarios – Xiaoming’s brother – Blogpark (CNblogs.com)

Implementation and Application of JS Front-end Singleton Pattern – Jianshu.com

Javascript singleton pattern (Lazy and Hungry) – Struggling Birds – Blogland (cnblogs.com)

Several ways to build a singleton pattern:

// The entire module defines an object


// Three methods of the instance pattern
// Create an instance from the start (hungry Guy mode)
function One() {
    if(! One.instance) {this.fn = () = > {
            console.log('> > > >')}this.balabala = 'QAQ'
        One.instance = this
    }
    return One.instance
}

// Create a method only when called (lazy mode)
function One() {
    if(One.instance) 
        return One.instance
    else {
        this.fn = () = > {
            console.log('> > > >')}this.balabala = 'QAQ'
        One.instance = this}}/ / class to create
class One {
    instance = null
    constructor() {
        this.balabala = 'hello'.this.fn = () = > {
            console.log('> > > >')}}static getInstance = () = > {
        if(!this.instance) {
            this.instance = new One()
        }
        return this.instance
    }
}
// The output result of the three methods
const a = One.getInstance()
const b = One.getInstance()
b.balabala = 'world'
console.log(a) //One { instance: null, balabala: 'world', fn: [Function (anonymous)] }
console.log(b) //One { instance: null, balabala: 'world', fn: [Function (anonymous)] }
console.log(a === b) //true
Copy the code

Design Pattern 3: Factory pattern

Simple Factory model

Also known as static factory methods, a factory object decides to create an instance of a product object class. Mainly used to create objects of the same class.

Also known as static factory methods, a factory object decides to create an instance of a product object class. Mainly used to create objects of the same class.

Example:

function factory(type) {
    function Atype() {
        this.value = 'A'
        this.view = 'Atype'
    }
    function Btype() {
        this.value = 'B'
        this.view = 'Btype'
    }
    function Ctype() {
        this.value = 'C'
        this.view = 'Ctype'
    }
    switch(type) {
        case 'A': {
            return new Atype()
            break;
        }
        case 'B': {
            return new Btype()
            break;
        }
        case 'C': {
            return new Ctype()
            break; }}}const a = factory('A') // a = {value:'A',view:'Atype'}
Copy the code

Because of this notation, every time we add or modify a class, we need to operate in two places, which is not very convenient, so we can optimize it

function factoryPro(type) {
    function Type(opt) {
        this.value = opt.value
        this.view = opt.view
    }
    switch(type) {
        case 'A': {
            return new Type({
                value: 'A'.view: 'Atype'
            })
            break;
        }
        case 'B': {
            return new Type({
                value: 'B'.view: 'Btype'
            })
            break;
        }
        case 'C': {
            return new Type({
                value: 'C'.view: 'Ctype'
            })
            break; }}}Copy the code

Factory method pattern

In this case, the factory is directly producing a class, but if there is no class, we need a “bottom” hint. At the same time, you can add a feature that flexibly handles both the direct method and the new method. Also, instead of creating a class directly from a function, we can add one

funtion factory(type) {
	if(this instanceof factory){
        	if(this[type]) {
                var a = new this[type]()
            	return a
            }
            else {
                / /... out}}else{
            return new factory(type);
    }
}
factory.prototype = {
    Atype: function() {
        this.value = 'A'
        this.view = 'Atype'
    },
    Btype: function() {
        this.value = 'B'
        this.view = 'Btype'
        this.fn = () = >{... }}}Copy the code

Abstract Factory pattern

This is a factory method where the class is an abstract class and the instance is implemented by subclass inheritance, function Agency (subType, superType) subType is a subclass and superType is an abstract class. The Agency method passes the abstract class to subclasses for inheritance. But I think you can do the same thing with the extends keyword, and for non-large projects, this pattern is not very useful.

Design Pattern 5: Appearance pattern (Compatibility pattern)

For a DOM click event, if we bind the onclick event to it, since the onclick method is a Dom0-level event, when someone else binds the motor event to the current DOM in this way, we duplicate the method and the previous onclick method will be overwritten.

In this case, we can use the method provided by DOM2 event handler, which is implemented immediately using addEventListener. However, browsers prior to IE9 or some of the less common browsers do not support this method, so we need to use attachEvent. If you encounter a browser that does not support a DOM2-level event handler, you can only bind the event using the onclick method.

To be compatible with these browsers, it is possible to use cosmetic mode: encapsulate complex low-level interfaces or logical judgments by defining a simpler high-level interface

// Appearance mode implements the click binding
function addEvent(dom, type, fn) {
    if(dom.addEventListener) {
		dom.addEventListener(type, fn, false) // The bubble phase is executed by default
    }else id(dom.attachEvent) {
        dom.attachEvent('on' + type, fn)
    }else {
		dom['on' + type] = fn 
    }
}
Copy the code

By appearance mode

Related development

About DOM Event Flow, DOM0 events and DOM2 Events – Cloud + Community – Tencent Cloud (Tencent.com)

Design Pattern 6: Proxy pattern

Instead of accessing the object directly, set up an intermediate object through which to access it indirectly. It controls the access path of objects with access and plays a role of distinguishing permissions and protecting objects.

Js Design Mode — Agent Mode — Magic Color — Bloggarden (CNblogs.com)

Design Pattern 7: Decorator pattern

On the basis of not changing the original object, it can be wrapped and expanded (adding attributes or methods) so that the original object can meet the user’s more complex needs. The key point is how to keep the original object unchanged while still can be expanded.

For example, we can extend the onclick method with decorator mode:

/ * * *@description Dom is the object to decorate, fn is the method to be extended */
const decorator = (dom, fn) = > {
	if(typeof dom.onclick === 'function') {
		const oldFn = dom.onclick
		dom.onclick = () = > {
            oldFn()
            fn()
        }
	}
    else {
        dom.onclick = fn
    }
}
Copy the code

Design Pattern 8: Observer pattern

The Observer pattern is such a design pattern. An object called the observed maintains a set of objects called the observers, which depend on the observed and automatically notify them of any changes in their state.

We can see observer subscribers on Vue and React. There are two practical approaches to achieving observer subscribers:

  • Object.defineProperty()

    The object.defineProperty () method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object.

Object.defineProperty(obj, 'value', {
    value: 12.get: () = > {
        console.log(obj.value)
    },
    set: (newVal) = > {
        console.log(newVal)
    }
})
Copy the code

This allows us to simply listen for queries and Settings on an object

  • Proxy

    As Vue3’s replacement for Object.defineProperty(), it implements listening on arrays and real-time listening on dynamically added objects. Proxy directly hijacks the object and returns the new object. Performance will be optimized accordingly

const handler = {
    get: (obj, prop) = > {
        if(obj[prop])
        console.log(obj[prop])
        return obj[prop]
    },
    set: (obj, prop, value) = > {
        obj[prop] = value;
        console.log(value)
        // Indicates success
    	return true; }}const p = new Proxy({}, handler);
Copy the code

Design Pattern 9: State pattern

When the internal state of an object changes, it causes a change in behavior that appears to change the object.

For example: when dealing with Ajax and encountering a different statusCode, we usually pass if.. Else to solve

if(res.statusCode === 200) {... }else if(res.statusCode === 401) {... }else if(res.statusCode === 403) {... }Copy the code

However, such continuous branching judgment is not optimal. We can create an object, and each condition is regarded as a state within the object. Facing different judgment results, it becomes a state within the selected object.

const StateFn =  function() {
    let status = {
        '200': function() {
            console.log('Status code 200, request successful')
            / /...
        },
        '401': function() {
            / /...
        },
        '403': function() {
            / /...}}const show = (code) = > {
        status[`${code}`]() && status[`${code}`]() 
    }
    return { show }
}

const stateFn = StateFn()
stateFn.show(200)
Copy the code

Here we use functions to create objects. After ES6, we can also use the class keyword.

Design architecture: MVC,MVP,MVVM

All three are common architectural patterns in software design.

MVC

The MVC pattern divides the architecture into three parts:

  • View: User interface.
  • Controller: service logic
  • Model: Data is saved

The communication mode between its various parts is: the view layer transmits instructions to the controller; The controller performs certain business logic, modifies the data, and then requires the Model layer to update the data preservation. The Model layer sends new data to the view and gets feedback.

MVP

There are three parts to the MVP architecture:

  • View: User interface.
  • Controller (Presenter) : business logic
  • Model: Data is saved

To avoid coupling between model and view, in MVP mode the model does not communicate with the view layer. Instead, the model and view layer communicate with the controller in both directions

Communication mode: The view layer loads data to the Presenter, and the Presenter loads data to the view layer. The Model layer becomes very simple, just receiving and loading data for presenters.

This mode decouples V and M and has a clear hierarchy.

MVVM

MVVM and MVP mode are very similar, except that the controller (Presenter) is changed to the ViewModel layer, which is used to achieve bidirectional binding with the View layer, which is conducive to fast update of the View layer when data is updated.

Diagrams of MVC, MVP and MVVM – Ruanyifeng.com