js design pattern-singleton pattern

Posted by Malkavbug on Thu, 09 May 2019 05:09:06 +0200

concept

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

UML class diagram

scene

Singleton pattern is a common pattern. We often need only one object, such as thread pool, global cache, window object in browser and so on.

In JavaScript development, the use of the singleton pattern is also very extensive. Imagine that when we click the login button, a login floating window will appear on the page, and the login floating window is unique. No matter how many times the login button is clicked, the floating window will only be created once, so the login floating window is suitable to be created in the singleton mode.

Advantages and disadvantages

Advantages: The responsibility for creating objects and managing singletons is distributed in two different ways

Realization

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)

This way of defining a global variable is very elegant and difficult to reuse code.

2. Use closures to implement singletons


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)

Some students may not understand closures very well. Here's how to implement them with functions.

3. Using Function to Realize Singleton


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)

2,3 These two methods also have drawbacks, that is, we have to call getInstance to create objects, generally we create objects using the new operator.

4. Transparent singleton model

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)

This approach also has some drawbacks: it does not conform to the single responsibility principle, and the object actually has two functions: singleton and object creation.

Now let's separate these two responsibilities.

5. Using Agent to Realize Singleton

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)

This approach also has some drawbacks: code can not be reused. If we have another object to use the singleton pattern, we have to write duplicate code.

6. Provide generic examples


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)

It's perfect to be here. Wait a minute. It's just es5. Now let's use es6 to implement it.

7. es6 singleton model

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)

Compare the above implementations

  1. The first method of using global variables should be abandoned.
  2. In the second way implemented with closures, instance instance instance objects are always created when we call Singleton.getInstance and should be discarded
  3. Other ways are inert singletons (created when needed)

The particularity of js

As we all know, JavaScript is actually a class-free language, and it makes no sense to copy the concept of singleton patterns.

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

There are several alternative ways to achieve this

1. Global variables

For example, var a = {}, then there is only one a object in the whole world.
But there are many problems with global variables, which can easily cause namespace pollution. We can solve them in the following two ways.

2. Use namespaces

  var namespace1 = {
    a: function () {
      alert(1);
    },
    b: function () {
      alert(2);
    }
  };

In addition, we can dynamically create namespaces


  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);
  // The above code is equivalent to:
  var MyApp = {
    event: {},
    dom: {
      style: {}
    }
  };

3. closure

  var user = (function () {
    var __name = 'sven',
      __age = 29;
    return {
      getUserInfo: function () {
        return __name + '-' + __age;
      }
    }
  })();

Example

Login box

Let's implement an example of clicking the login button to pop up the login box.

Rough Realization

<html>

<body>
  <button id="loginBtn">Sign in</button>
</body>
<script>
  var loginLayer = (function () {
    var div = document.createElement('div');
    div.innerHTML = 'I'm a login window';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
  })();
  document.getElementById('loginBtn').onclick = function () {

    loginLayer.style.display = 'block';
  };
</script>

</html>

In this way, if the user does not click the login button, the login box will also be created at the beginning.

Improvement


<html>

<body>
  <button id="loginBtn">Sign in</button>
</body>
<script>
  var createLoginLayer = function () {
    var div = document.createElement('div');
    div.innerHTML = 'I'm a login 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>

In this way, a login box is created every time a button is clicked.

Further improvement


var createLoginLayer = (function () {
    var div;
    return function () {
      if (!div) {
        div = document.createElement('div');
        div.innerHTML = 'I'm a login window';
        div.style.display = 'none';
        document.body.appendChild(div);
      }
      return div;
    }
  })();

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

This method is not universal enough and does not conform to the principle of single responsibility.

Further improvement


  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 = 'I'm a login 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';
  };

  //Now let's try to 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';
  };

So far it has been perfect.

Topics: Javascript