understand

Ensure that a class has only one instance and provide a global access point to access it. It can also be understood as sharing a global instance in our application.

scenario

When the button is clicked, a float window appears on the page, and this float window is unique. No matter how many times the button is clicked, the float window will only be created once, so this float window is suitable for singleton mode.

implementation

A variable is used to indicate whether an object has been created for a class, and if so, the next time an instance of the class is fetched, the previously created object is returned.

Traditional object-oriented language implementation

In traditional object-oriented languages, singletons are created from “classes”. (Now, I should warn you that this doesn’t always work in JavaScript, but it’s important to know)

const Singleton = function(name){ this.name = name; this.instance = null; / / variable tag} Singleton. Prototype. GetName = function () {alert (enclosing name); } Singleton.getInstance = function(name){ if(! this.instance){ this.instance = new Singleton(name); } return this.instance// return the previously created object} const a = singleton.getInstance ('hearling1'); const b = Singleton.getInstance('hearling2'); alert(a === b); // trueCopy the code

Transparent singleton mode

Users can create objects from this class just like they would from any other normal class.

var CreateDiv = (function () { var instance; Var CreateDiv = function (HTML) {// Singleton if (instance) {return instance; } this.html = html; // create object this.init(); Return (instance = this); // Make sure there is only one object}; CreateDiv.prototype.init = function () { var div = document.createElement("div"); div.innerHTML = this.html; document.body.appendChild(div); }; return CreateDiv; }) (); var a = new CreateDiv("Healing1"); var b = new CreateDiv("Healing2"); alert(a === b); // tureCopy the code

To encapsulate instance, we use self-executing anonymous functions and closures, and have this anonymous function return the real Singleton constructor, which adds some complexity to the program and makes it uncomfortable to read.

If you know anything about the specification, you will know that it violates the “single responsibility principle” and that it is a bad practice that needs to be improved.

The proxy implements the singleton pattern

To split the above code, you can use a proxy:

var CreateDiv = function (html) { this.html = html; this.init(); }; CreateDiv.prototype.init = function () { var div = document.createElement("div"); div.innerHTML = this.html; document.body.appendChild(div); }; ProxySingletonCreateDiv var proxySingletonCreateDiv = (function () {var instance; return function (html) { if (! instance) { instance = new CreateDiv(html); } return instance; }; }) (); var a = new ProxySingletonCreateDiv("Healing1"); var b = new ProxySingletonCreateDiv("Healing2"); alert(a === b); // tureCopy the code

We have also written a singleton pattern by introducing the proxy class, but now we have moved the logic responsible for managing the singleton into proxySingletonCreateDiv. This makes CreateDiv a generic class, which is combined with proxySingletonCreateDiv to achieve singleton effect.

The singleton pattern in JavaScript

In the traditional object-oriented language implementations mentioned earlier, singletons are created from “classes.” But JavaScript is really a class-free language, and for that reason, the concept of a singleton pattern doesn’t make sense. The way to create an object in JavaScript is very simple, since we only need a unique object, why create a “class” for it first?

Inert singleton

In JavaScript, we can implement the singleton pattern like this, without creating a class for it:

var createLoginLayer = (function () { var div; return function () { if (! div) { div = document.createElement("div"); Div.innerhtml = "This is a 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

Generic lazy singleton

Problems with the previous code:

  • This code still violates the single responsibility principle, as the logic for creating objects and managing singletons is placed increateLoginLayerInside the object
  • If we need to create a unique on the page next timeiframeOr,scriptTags, used to request data across domains, must do the samecreateLoginLayerThe function is almost copied, like this:
var createIframe = (function () { var iframe; Iframe return function () {if (! iframe) { div = document.createElement("div"); Div.innerhtml = "This is a window "; div.style.display = "none"; document.body.appendChild(div); } return iframe; }; }) ();Copy the code

Isolate the invariant parts and use a variable to indicate whether the object was created:

var obj; if(! obj){ obj = xxx; // If so, return the created object next time}Copy the code

Encapsulate, encapsulate the logic inside the getSingle function, and create an object method, as props:

var getSingle = function(fn){
  var result;
  return function(){
  	return result || (result = fn.apply(this,arguments))
  }
}
Copy the code

The benefits of writing this, and code:

  • Various object creation functions can be passed
  • getSingleReturns the new function and usesresultTo save the results of fn’s calculations without being destroyed (closure)
  • In future requests, ifresultHas been assigned, then it will return that value
var createLoginLayer = function(){ var div = document.createElement('div'); Div.innerhtml = 'This is a window '; div.style.display = 'none'; document.body.appendChild(div); return div; }; var createSingelLoginLayer = getSingle(createLoginLayer); document.getElementById('loginBtn').onclick = function(){ var loginLayer = createSingelLoginLayer(); loginLayer.style.display = 'block'; }Copy the code

The singleton pattern USES far more than just creating objects, for example, we often after rendering the page for a list, next to the click event, the list of binding if it is through ajax dynamic into the list of additional data, on the premise of using event agency, click event actually need only in rendering a list for the first time be bound once, But we don’t want to determine if this is the first time we’ve rendered the list. We can use getSingle to do the same:

var bindEvent = getSingle(function () {
  document.getElementById("div1").onclick = function () {
    alert("click");
  };
  return true;
});
Copy the code

summary

The singleton pattern is a simple but very useful pattern, especially the lazy singleton technique, where only unique objects are created when appropriate. Even more wonderful, the responsibility for creating objects and managing singletons is split between two different methods, which together have the power of the singleton pattern.

The practical application

The singleton pattern is typically used to share a global instance in our application.

A singleton is a class that can be instantiated once and can be accessed globally. This single instance can be shared across our application, making the singleton ideal for managing global state in the application.

Class implements

First, let’s take a look at what a singleton looks like using the ES5 class. For this example, we’ll build a Counter class:

  • Create a file namedinstanceThe variables.
  • inCounterConstructor, which we can use when creating new instancesinstanceSet to a reference to an instance.
  • We can prevent new instantiations by checking if the instance variable already has a value.
  • If this is the case, the instance already exists. This shouldn’t happen: an error is thrown to let the user know
let instance; let counter = 0; class Counter{ constructor(){ if(instance){ throw new Error('You can only create one instance') } instance = this; } getInstance(){ return this; } getCount(){ return counter; } increment(){ return ++counter; } decrement(){ return --counter; }}Copy the code

Verify:

const counter1 = new Counter();
const counter2 = new Counter();
console.log(counter1.getInstance() === counter2.getInstance());
//Error: You can only create one instance
Copy the code

Good, we can’t create multiple instances anymore.

Note that when we export the Counter instance, we should freeze. The object. freeze method ensures that consuming code cannot modify the Singleton. Properties cannot be added or modified on a frozen instance, reducing the risk of accidentally overwriting values on a Singleton.

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;
Copy the code

General object implementation

Changing the above class implementation to use regular objects, let’s use the same example we saw before. This time, however, the counter is just an object containing the following:

let count = 0; const counter{ increment(){ return ++counter; }, decrement(){ return --counter; }}; Object.freeze(counter); export {counter};Copy the code

Counter code in detail: codesandbox. IO/embed/compe…

Since objects are passed by reference, both Redbutton.js and blueButton.js are importing a reference to the same singletonCounter object. Changing the value of count in both files changes the value on singletonCounter, which is visible in both files.

OK The above is a simple example to understand. In practice, the Counter example application would look something like this:

We can see that when we call increment in redbutton.js or bluebutton.js, the value of the Counter property on the Counter instance is updated in both files. It doesn’t matter if we click the red or blue button: all instances share the same value. That’s why the counter keeps incrementing by one, even if we call the method in a different file.

The advantages and disadvantages

  • Limiting the instantiation to just one instance can save a lot of memory. Instead of setting up memory for a new instance each time, we just set up memory for that instance, which is referenced throughout the application. However, Singleton is actually considered an anti-pattern and can be (or… Should be) avoided in JavaScript.
  • In many programming languages, such as Java or C++, it is not possible to create objects directly as it is in JavaScript. In those object-oriented programming languages, we need to create a class that creates an object. The created object has the value of the class instance, just like the instance value in the JavaScript example.
  • However, the class implementation shown in the example above is actually overcorrecting. Since we can create objects directly in JavaScript, we can simply use regular objects to achieve exactly the same result. Let’s go through some of the disadvantages of using singletons!

The problem

Using singletons incorrectly can cause problems, such as in the example I wrote:

test

Testing singleton-dependent code can get tricky. Since we cannot create a new instance every time, all tests depend on changes to the global instance from the previous test. In this case, the order of the tests is important, and a small change can cause the entire test suite to fail. After the test, we need to reset the entire instance to reset the changes made by the test.

Depend on the hidden

When importing another module, in this case Supercounter.js, the module importing Singleton may not be obvious. In other files, such as index.js in this case, we might be importing the module and calling its methods. Thus, we accidentally changed the value in the Singleton. This can lead to unexpected behavior because multiple instances of Singleton can be shared across the application, and these instances can also be modified.

import Counter from "./counter"; export default class SuperCounter { constructor() { this.count = 0; } increment() { Counter.increment(); return (this.count += 100); } decrement() { Counter.decrement(); return (this.count -= 100); } } //index.js const counter = new SuperCounter(); counter.increment(); console.log("Counter in counter.js: ", Counter.getCount()); //Counter in counter.js: 1Copy the code

The global behavior

A singleton instance should be able to be referenced throughout the application. Global variables exhibit essentially the same behavior: since global variables are available globally, we can access them throughout the application.

Having global variables is often considered a bad design decision. Global scope contamination can end up accidentally overwriting the value of a global variable, leading to many unexpected behaviors.

Creating global variables is fairly uncommon in ES2015. The new let and const keywords prevent developers from accidentally contaminating the global scope by keeping variables declared using these keywords within the block scope. The new module system in JavaScript makes it easier to create globally accessible values without contaminating the global scope by being able to export values from modules and import those values into other files.

However, a common use case for singletons is to have some kind of global state throughout the application. Having multiple parts of your code base depend on the same mutable object can lead to unexpected behavior.

Typically, some parts of the code base modify values in the global state, while others use that data. The order of execution here is important: we don’t want to accidentally consume data first when we don’t have any to consume. As applications grow and dozens of components depend on each other, understanding data flow can become tricky when using global state.

In React, we often rely on global state through state management tools such as Redux or React Context rather than using Singletons. Although their global state behavior may look like a singleton, these tools provide read-only state rather than mutable state for singletons. With Redux, only the pure function Reducer can update the state after the component sends an operation through the scheduler.

While using these tools won’t magically eliminate the shortcomings of global state, we can at least ensure that global state changes the way we want it to, since components can’t update state directly.

conclusion

The Singleton Pattern is a creation Pattern that provides an optimal way to create objects. Ensure that there is only one instance and provide a global access point to access it. Use when you want to control the number of instances and save memory.

Advantages: Having only one instance in memory improves performance by reducing frequent instance creation and destruction.

Disadvantages: Not suitable for objects that change frequently (such as count above) and may have problems with testing and data flow (disadvantages described in the previous section). If it is not used for a long time after creation, it will be garbage collected.

🌸 thank you very much for reading this, if you feel good, give a thumbs up ⭐ ~~ today is also trying to become strong and bald HearLing 💪 🌸