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

  1. The first method, using global variables, should be rejected
  2. In the second approach to closure implementation, the instance object is always created when we call Singleton. GetInstance and should be discarded
  3. 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