How to use singleton pattern in JavaScript

If you want your code to be more elegant, maintainable, and concise, you often cannot do without the solution of 

design patterns .

In the JS design pattern, the core idea is to encapsulate changes (separate the changes from the unchanged, ensuring that the changed parts are flexible and the unchanged parts are stable).

Singleton pattern

So let’s talk about the first common design pattern: singleton pattern .

The singleton mode ensures that a class has only one instance and provides a global access method to access it. In order to solve the problem of a globally used class being frequently created and destroyed and occupying memory.

Through closures in ES5

In ES5, you can use closures (the internal return function of the function is referenced by external variables, causing the variables in this function to not be released, so it is constructed as a closure) to save instances of this class.

var Singeton = (function(){
     var instance;
    
    function User(name,age){
        this.name=name;
        this.age=age;
    }
    
    return function(name,age){
        if(!instance){
            instance = new User(name,age)
        }
        return instance
    }
})()

Once this instance is generated, it will be returned every time and will not be modified. You can see the following code. When the User object is initially assigned name: alice, age: 18, subsequent assignments will be invalid. And each return is the initial instance object.

Using static properties of classes in ES6

The above code is implemented using ES6 syntax, and the unique instance object is saved through the static properties of the class.

  class Singeton {
    constructor(name,age){
        if(!Singeton.instance){
            this.name = name;
            this.age = age;
            Singeton.instance = this;
        }

       return Singeton.instance;
    }
}

The creation method is still the same, using the new keyword to create an instance object of the class.

Case

So what is the practical use of such a design pattern in development? Let’s imagine this business scenario: When visiting a website, the page has not been operated for a long time. At this time, the authorization expires. When we click anywhere on the page, a login box will pop up.

Then this login box is globally unique, there will not be multiple copies, and they will not conflict with each other, so there is no need to create one every time, just keep the initial one.

Create nodes in advance

We may think of first creating nodes in the page in advance, writing the page style, and finally controlling the display attribute of the element to achieve the effect of displaying and hiding.

< div  class = "modal" > Login dialog </ div > 
< button  id = "open" > Open </ button > 
< button  id = "close" > Close </ button >

< style > .modal {
     display : none;
     /* Other layout codes are omitted*/
  
  }
</style>

<script>
  document.querySelector("#open").onclick = function(){
     const modal = document.querySelector('.modal')
     modal.style.display = 'block'
  }
</script>

This can fulfill the requirement. There is only one login box globally, and the same one is displayed every time. But the problem is that the DOM element is created and added to the body from the beginning. Regardless of whether it is needed or not, if some scenes do not require login, then the initial rendering here will waste space.

Singleton pattern

So how should we implement a singleton pattern that does not require initial rendering, is used only when needed, and returns the same instance every time?

We can handle it like this

<!-- Remove tags with class modal and create them dynamically -->

<script>
const Modal = (function(){
  let instance = null
  return function(){
      if(!instance){
          instance = document.createElement("div")
          instance. innerHTML = "Login Dialog" 
          instance. className = "modal" 
          instance. style . display = "none" 
          document . body . appendChild (instance)
      }
      return instance
  }
})()

document.querySelector("#open").onclick = function(){
       //Create modal. If placed outside, the element will be created at the beginning 
      const modal = Modal ()    
       //Display modal 
      modal. style . display = "block"
  }

  document.querySelector("#close").onclick = function(){
      const modal = Modal()    
      modal.style.display = "none"
  }
</script>

Although the above method can achieve the effect, the logic of creating objects and managing singletons is placed inside the object, which is a bit confusing. And if you need to create the only iframe or script tag in the page next time, you will have to copy the above function.

Generic singleton

First, split the function logic and take out the logic that creates the object.

const createLayer = function(){
  let div = document.createElement("div")
  div. innerHTML = "Login dialog" 
  div. className = "modal" 
  div. style . display = "none" 
  document . body . appendChild (div);
   return div;
}

const Modal = (function(){
  let instance = null
  return function(){
      if(!instance){
          instance = createLayer()
      }
      return instance
  }
})()

After the above modification, the code logic will be clearer, but at this time, it does not support the general creation of other components. At this time, we want to think about how to optimize the method of creating a singleton, and whether we can abstract the functions that the singleton needs to execute. change.

const createSingle = (function(fn){
    let instance; 
    return function(){
        return instance || ( instance = fn.apply(this, arguments))
    }
})()

After this transformation, if there is a method to create an iframe, you can use it directly.

const createIframe = function() {
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  document.body.appendChild(iframe);
  return iframe
}
const singleIframe = createSingle(createIframe)

document.querySelector("#open").onclick = function(){
 const iframe = singleIframe()
 iframe.style.display = 'block'
}

Practical application

The above are all our small trials, let’s take a look at some great implementations in the community~ For example: Redux, a commonly used state management tool in React, uses the singleton mode, which has such requirements.

  • Single data source: The entire application state exists in only one store.
  • State is read-only: do not change the value of state directly. The only way to change state is to trigger an action.
  • Reducers are pure functions: you need to write a pure function reducer to modify the value of state.

Let’s take a look at the source code of Redux. Some logical judgments and comments have been deleted for ease of reading. You can see that the currentState in the closure is obtained each time through the store’s getState method.

The singleton mode has only one instance in the memory, which can reduce memory expenses, and can also set global access points in the system to optimize and share resources.