JavaScript Design pattern learning and practice (1)

With the knowledge of JS and the actual development needs, the heart for re-reading Design pattern feeling more and more deep, so I picked up the “JavaScrip Design pattern and development practice” a book, and combined with the “Design pattern” part of the content, first make a summary, and first talk about the singleton pattern, the rest of the follow-up slowly.

First of all, I would like to raise a few questions, which are also my summary gains this time:

  • Is JavaScript Object Oriented or Process Oriented?
  • Is the function a first-class citizen Object Oriented or Process Oriented?

Then throw out one of my biggest gains from Design pattern this time, that is, all Design patterns are actually identifying the invariant parts and variable parts of the code, encapsulating them separately, and then combining the two. To accurately identify this, not only do you need deep code capabilities, but you also need to truly understand business practices.

For example, in the strategy pattern, each strategy is a volatile part, while the call to the policy is an invariant part. In the proxy pattern, the operations of the proxied are immutable, while the proxy itself is volatile.

In fact, the emergence of Design patterns is to some extent to make up for the shortcomings of the language. For example, the prototype pattern is a Design pattern, but JavaScript itself supports this pattern through the prototype chain.

Is JavaScript Object Oriented or Process Oriented?

First of all, in the recent related learning, I gradually understand some of the concepts that were confused in the past, such as JavaScript is Object Oriented and Process Oriented?

Object Oriented Programming is to establish the connection and cooperation mode between classes. Procedural Programming is to establish one process after another, and each process deals with one or several classes. For example, process-oriented programming is like constructing a process. Teach a child language, and then construct a process to teach him math. Object Oriented is to establish the relationship between children and language and mathematics

In fact, there is no strict official definition of procedural programming and procedural programming languages. The best way to understand these two concepts is to compare them with Object Oriented Programming and Object Oriented Programming Languages. In contrast to Object Oriented Programming, which uses classes as the basic unit of organizing code, procedural programming uses procedures (or methods) as the basic unit of organizing code. Its main feature is the separation of data and methods. Compared with Object Oriented programming languages, the biggest feature of procedural programming languages is that they do not support rich Object Oriented programming features, such as inheritance, polymorphism, and encapsulation.

That is to say, theoretically providing inheritance, polymorphism, and encapsulation languages can be considered Object Oriented languages, so is JavaScript an Object Oriented language?

The author personally believes that JavaScript is an Object Oriented language, but its inheritance is not through the form of classes, but through the prototype chain. Although ES6 also implements the syntax of classes, it is essentially through the prototype chain. way **.

First of all, inheritance, what is the difference between inheritance through class and prototype chain? ** The way through class is an is-a relationship, that is to say, the subclass is a kind of parent class, and prototype chain inheritance is a bit similar to interface (has-a), or a bit like duck type, that is, I need a variable now, I am more concerned about whether it has the say method, not whether he is an instance of Duck, even if he is an instance of Chicken, he has the say method, that is what I want **.

The essence of prototype chain inheritance is the delegation mechanism of prototype chain. To get an object, instead of instantiating a class, find an object as a prototype and clone it. The object will delegate the request to the prototype of its constructor. For example, JavaScript provides the way Object.create **

Interface-oriented programming is the most important idea in Design Pattern, but in JavaScript, because it is inherited based on the prototype chain, it inherently implies the idea of interface-oriented programming. Interface-oriented programming is different from mainstream languages and is simpler.

Let’s talk about polymorphism. Polymorphism means that a unified operation acts on different objects, which can produce different interpretations and different execution results. In other words, when sending the same message to different objects, these objects will give different feedback based on the message.

The idea behind polymorphism is to separate “what to do” from “who does it and how to do it”, that is, to separate “food that doesn’t change” from “things that can change”. Separating the two, encapsulating them separately, gives us the ability to extend programs, and programs seem to grow, which is also consistent with the Open Closed Principle.

Using inheritance to achieve polymorphism is one of the most common ways to make objects exhibit polymorphism. Inheritance is divided into implementation inheritance and interface inheritance. The former is based on parent-child classes, and the latter is through interfaces.

The essence of polymorphism is to separate what to do and who to do it. To achieve this, we need to eliminate the coupling between types first. In Java, we need to achieve it through upward transformation, while in JavaScript, variable types are variable at runtime. An object can be either a Duck type or a Chicken type. What I need is that it says this method, which means ** In JavaScript, object polymorphism is innate **.

The fundamental benefit of polymorphism is that you no longer have to ask the object “what type are you” and then call a certain behavior of the object based on the answer, you just call it, and all other polymorphism mechanisms will be arranged for you. In other words, the most fundamental role of polymorphism is to eliminate procedural conditional branching statements by converting them into object polymorphism.

What is function programming

In function programming, a function is a first-class object or first-class function, which means that a function can either be used as an input parameter value for other functions, or return a value from a function, be modified, or be assigned to a variable. The lambda calculus is the most important foundation of this paradigm. The function of lambda calculus can accept function as an input parameter and output return value.

Compared with imperative programming, function programming emphasizes the result of program execution rather than the process of execution. It advocates the use of several simple execution units to make the calculation results gradually and derive complex operations layer by layer, rather than designing a complex execution process.

Therefore, whether it is function-oriented programming does not affect whether it is Object Oriented or process-oriented, but function-oriented programming will have an impact on the code when we specifically implement some Design patterns, that is to say, Design pattern is a kind of Ideological level, combined with specific language implementations, and function programming language function can be used as imported parameter and exported parameter, which will have an impact on JavaScript implementation of Design pattern.

Singleton mode

Let’s first introduce the definition of the singleton pattern: ensuring that a class has only one instance and providing a global access point to access it

Implement singleton pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Singleton = function(name) {
this.name = name;
this.instance = null;
}

Singleton.prototype.getName = function() {
alert(this.name);
}

Singleton.getInstance = function(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}

var a = Singleton.getInstance('sun1');
var b = Singleton.getInstance('sun2');

alert(a = b) // true

We use Singleton.getInstance to get the unique object of the class. This method is relatively simple, but there is a problem. We increase the uncertainty of this class. The user of the Singleton class must know that this is a singleton class, and pass It is different from the previous new XXX way to get the object

Transparent singleton pattern

Our goal now is to implement a transparent singleton class from which users can create objects just like any other ordinary class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var CreateDiv = (function(){
var instance;

var CreateDiv = function(html) {
if (instance) {
return instance;
}

this.html = html;
this.init();
return instance = this;
}

CreateDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}

return CreateDiv;
})();

var a = new CreateDiv('div');
var b = new CreateDiv('div');

alert(a = b) // true

To encapsulate the instance, we use a self-executing anonymous function and closure, and make this anonymous function return the constructor of the real Singleton, which adds some complexity to the program and is not very comfortable to read.

And we observe the constructor function of Singleton:

1
2
3
4
5
6
7
8
9
var CreateDiv = function(html) {
if (instance) {
return instance;
}

this.html = html;
this.init();
return instance = this;
}

In this code, the constructor function of CreateDiv is actually responsible for two things, the first is to create an object and execute the initialization init method, and the second is to ensure that there is only one object, ** which does not comply with the Single Responsibility Principle **.

Suppose one day we want to use this class to create thousands of divs in the page, that is, to make this class a normal class, we must rewrite the constructor of CreateDiv to remove the code that creates a unique object, which violates the Open Closed Principle.

Singleton pattern implemented through proxy

Now we solve the above problem by introducing a proxy. First, we remove the code responsible for managing the singleton from the constructor function of CreateDiv and make it a normal class.

1
2
3
4
5
6
7
8
9
10
11
var CreateDiv = function(html) {
this.html = html;
this.init();
return instance = this;
}

CreateDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}

Then we introduce the proxy class, proxySingletonCreateDiv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var proxySingletonCreateDiv = (function() {
var instance;

return function(html) {
if (!instance) {
instance = new CreateDiv(html);
}

return instance;
}
})()

var a = proxySingletonCreateDiv('div');
var b = proxySingletonCreateDiv('div');

alert(a = b); // true

Singleton pattern in JavaScript

The implementation of the singleton pattern above is more close to the implementation in traditional Object Oriented languages, where singleton objects are created from “classes”. In class-centric languages, this is a natural approach. For example, in Java, if you need an object, you must first define a class, and the object always comes from the class.

JavaScript is actually a classless language, which is why the concept of the singleton pattern does not make sense. Creating objects in JavaScript is very simple. Since we need a unique object, why should we create a class for it in the first place? This is superfluous.

Remember, the core of the singleton pattern is to ensure that there is only one instance and provide global access.

Global variables are not singleton mode, but in JavaScript, we often use global variables to implement singleton mode.

1
var a = {};

This a is a singleton that satisfies only one instance, and if it is declared in the global scope, it can be accessed globally.

But there are many problems with global variables, such as causing namespace pollution, in large and medium-sized projects, if poor management, there may be many such variables in the program, as ordinary developers, we should try to reduce the use of global variables, even if we want to Use, but also try to reduce its impact.

We can use the following ways to reduce the naming pollution caused by global variables:

** 1. Use namespaces **

Proper use of namespaces will not eliminate global variables, but it can reduce the number of global variables. The simplest way is still to use object literals.

1
2
3
4
5
6
7
8
var namespace1 = {
a: function() {
alert(1);
},
b: function() {
alert(2);
}
}

** 2. Encapsulate private variables with closures **

This method encapsulates some variables inside the closure, only exposing some interfaces to communicate with the outside world.

1
2
3
4
5
6
7
8
9
10
var user = (function(){
var __name = 'sun',
__age = 26;

return {
getUserInfo: function() {
return __name + '-' + __age;
}
}
})()

Inert singleton

Earlier, we learned about some implementations of the singleton pattern. In this section, we will learn about lazy singletons.

Lazy singletons refer to creating instances of objects only when needed. Lazy singletons are the focus of the singleton pattern, and this technique is very useful in development.

In fact, this is how we used Singleton.getInstance at the beginning

1
2
3
4
5
6
7
8
9
10
11
var Singleton.getInstance = (function(){
var instance;

return function(name) {
if (!instance) {
instance = new Singleton(name);
}

return instance;
}
});

However, this is a class-based singleton pattern. As mentioned earlier, the “class” -based singleton pattern does not apply in JavaScript

Let’s imagine a scenario, click the login button and then create a globally unique login float window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
<body>
< button id = "loginBtn" > Login </button >
</body>
</html>

<script>
var createLoginLayer = (function() {
var div;
return function() {
if (!div) {
div = document.createElement('div');
div.innerHTML = "Landing Floating Window";
div.style.display = 'none';
document.body.appendChild(div);
}

return div;
}
})()

document.getElementById('loginBtn').onclick = function() {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
}
</script>

Generic lazy singleton

In the previous section, we completed a working lazy singleton, but we found the following problems:

  • This code still violates the Single Responsibility Principle, and the logic for creating objects and managing singletons is placed inside the createLoginLayer object
  • If we need to create a unique iframe or script in the page next time, then we must almost copy the createLoginLayer function

We need to isolate the invariant part. Regardless of how much difference there is between creating a div and iframe, the logic of managing singletons can be extracted. This logic is always consistent. Use an object flag to indicate whether an object has been created. If so, return the created object directly next time:

1
2
3
4
var obj;
if (!obj) {
obj = xxx;
}

We will now extract the logic for managing singletons from the original code. These logic are encapsulated inside the getSingle function, and the creation method fn is dynamically passed into getSingle as a parameter.

1
2
3
4
5
6
var getSingle = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments));
}
}

Next, we can say that the method used to create the login floating window passes getSingle in the form of parameter fn. We can not only pass createLoginLayer, but also pass createScipt, etc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var createLoginLayer = function() {
var div = document.createElement('div');
div.innerHTML = "Landing Floating Window";
div.style.display = 'none';
document.body.appendChild(div);
return div;
}

var createSingleLoginLayer = getSingle(createLoginLayer);

document.getElementById('loginBtn').onclick = function() {
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
}

In this way, we put the responsibility of creating instance objects and the responsibility of managing singletons in two method cases, which can change independently without affecting each other, and together complete the creation of singletons.