preface
This series of articles is based on JavaScript Design Patterns and Development Practices, with a little thought added. I hope it helps.
Article series
Js design pattern — singleton pattern
Js Design pattern — Policy pattern
Js design pattern — proxy pattern
concept
The singleton pattern is defined by ensuring that there is only one instance of a class and providing a global point of access to it.
UML class diagrams
scenario
The singleton pattern is a common pattern, and there are some objects that we usually only need one, such as thread pools, the global cache, window objects in browsers, and so on.
In JavaScript development, the singleton pattern is also very versatile. Imagine that when you click the login button, a login float window appears in the page, and that the login float window is unique, no matter how many times you click the login button, the login float window should be created using the singleton pattern.
The advantages and disadvantages
Advantages: The responsibility for creating objects and managing singletons is distributed in two different methods
implementation
1. Our first singleton
var instance = null
var getInstance = function(arg) {
if(! instance) { instance = arg }return instance
}
var a = getInstance('a')
var b = getInstance('b')
console.log(a===b)
Copy the code
This is an inelegant way to define a global variable, and it’s not easy to reuse code
2. Implement singletons with closures
var Singleton = function( name ){
this.name = name;
};
Singleton.getInstance = (function(){
var instance = null;
return function( name ){
if ( !instance ){
instance = new Singleton( name );
}
return instance;
}
})();
var a = Singleton.getInstance('a')
var b = Singleton.getInstance('b')
console.log(a===b)
Copy the code
For those of you who don’t understand closures, let’s implement them using functions
3. Use functions to achieve singletons
function Singleton(name) {
this.name = name
this.instance = null
}
Singleton.getInstance = function(name) {
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
var a = Singleton.getInstance('a')
var b = Singleton.getInstance('b')
console.log(a===b)
Copy the code
The downside to both methods is that we have to call getInstance to create the object, and we usually create the object using the new operator
Transparent singletons
var Singleton = (function() {
var instance
Singleton = function(name) {
if (instance) return instance
this.name = name
return instance = this
}
return Singleton
})()
var a = new Singleton('a')
var b = new Singleton('b')
console.log(a===b)
Copy the code
There is a downside to this approach: instead of conforming to the single responsibility principle, the object is actually responsible for two functions: singletons and creation objects
Let’s separate these two duties
5. Implement singletons using proxies
var People = function(name) {
this.name = name
}
var Singleton = (function() {
var instance
Singleton = function(name) {
if (instance) return instance
return instance = new People(name)
}
return Singleton
})()
var a = new Singleton('a')
var b = new Singleton('b')
console.log(a===b)
Copy the code
There is a downside to this approach: the code is not reusable. If we had another object that would also take advantage of the singleton pattern, we would have to write duplicate code
6. Provide generic singletons
var People = function(name) {
this.name = name
}
var Singleton = function(Obj) {
var instance
Singleton = function() {
if (instance) return instance
return instance = new Obj(arguments)}return Singleton
}
var peopleSingleton = Singleton(People)
var a = new peopleSingleton('a')
var b = new peopleSingleton('b')
console.log(a===b)
Copy the code
So this is pretty good, wait a minute this is just es5, so let’s do it with ES6
7. Es6 singleton pattern
class People {
constructor(name) {
if (typeof People.instance === 'object') {
return People.instance;
}
People.instance = this;
this.name = name
return this; }}var a = new People('a')
var b = new People('b')
console.log(a===b)
Copy the code
Compare the above implementations
- The first method, using global variables, should be rejected
- In the second approach to closure implementation, the instance object is always created when we call Singleton. GetInstance and should be discarded
- All other methods are lazy singletons (created when needed)
The particularity of JS
As we all know, JavaScript is a class-free language, and the concept of singletons doesn’t make sense.
At its core, the singleton pattern ensures that there is only one instance and provides global access.
We can do this in a few ways
1. Global variables
For example, if var a = {}, then there is only one global object a. However, there are many problems with global variables, which can easily cause namespace pollution. We can solve them in the following two ways
2. Use the namespace
var namespace1 = {
a: function () {
alert(1);
},
b: function () {
alert(2); }};Copy the code
Additionally, namespaces can be created dynamically
var MyApp = {};
MyApp.namespace = function (name) {
var parts = name.split('. ');
var current = MyApp;
for (var i in parts) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
};
MyApp.namespace('event');
MyApp.namespace('dom.style');
console.dir(MyApp);
// This code is equivalent to:
var MyApp = {
event: {},
dom: {
style: {}}};Copy the code
3. The closure
var user = (function () {
var __name = 'sven',
__age = 29;
return {
getUserInfo: function () {
return __name + The '-'+ __age; }}}) ();Copy the code
example
The login dialog
Let’s implement an example of clicking the login button to pop up the login box
Rough implementation
<html>
<body>
<button id="loginBtn">The login</button>
</body>
<script>
var loginLayer = (function () {
var div = document.createElement('div');
div.innerHTML = 'THIS is the login float window';
div.style.display = 'none';
document.body.appendChild(div);
returndiv; }) ();document.getElementById('loginBtn').onclick = function () {
loginLayer.style.display = 'block';
};
</script>
</html>
Copy the code
This method also creates a login box in the first place if the user does not click the login button
To improve the
<html>
<body>
<button id="loginBtn">The login</button>
</body>
<script>
var createLoginLayer = function () {
var div = document.createElement('div');
div.innerHTML = 'THIS is the login float window';
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
document.getElementById('loginBtn').onclick = function () {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
</script>
</html>
Copy the code
This creates a login box each time the button is clicked
To improve
var createLoginLayer = (function () {
var div;
return function () {
if(! div) { div =document.createElement('div');
div.innerHTML = 'THIS is the login float window';
div.style.display = 'none';
document.body.appendChild(div);
}
return div;
}
})();
document.getElementById('loginBtn').onclick = function () {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
Copy the code
This method is not universal enough and does not conform to the principle of single responsibility
To improve again
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this.arguments)); }};var createLoginLayer = function () {
var div = document.createElement('div');
div.innerHTML = 'THIS is the login float 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';
};
// Create a unique iframe for dynamically loading third-party pages:
var createSingleIframe = getSingle(function () {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
return iframe;
});
document.getElementById('loginBtn').onclick = function () {
var loginLayer = createSingleIframe();
loginLayer.src = 'http://baidu.com';
};
Copy the code
It’s perfect now