preface

I’ve been learning JavaScript design patterns recently, and I’m writing this article to help me consolidate my knowledge and share what I’ve learned and learned. I always thought that design patterns should be hard and I would never learn them. It was not until I read this booklet: Core Principles and Application Practices of JavaScript Design Patterns in winter vacation that I found that design patterns are not so advanced, but they are used all the time, but I did not know that they are called design patterns. In the process of learning this pamphlet, there was not too much effort. Instead, the whole process was one Epiphany after another: “Oh, I used this thing before, and his name is XXXX”. Of course, this booklet is just an entry point, but the author also encourages us to broaden our horizons and solidify our coding skills. So I went out and bought another book called JavaScript Design Patterns and Development Practices, hoping to learn something. The context for this article is what I wrote after reading the singleton patterns chapter in the book.

The basic idea of the singleton pattern

The definition of a singleton pattern is:

A class has only one instance and provides a global access point to it

If it’s a little confusing to look directly at the definition, let’s look at the simplest implementation of the singleton pattern

function SingleDog (name) {
  this.name = name
}
// Get the instance method
SingleDog.getInstance = (function () {
  let instance = null
  return function () {
    // Save the instance with the closure, so you can see if it has been instantiated
    if(! instance) {return instance = new SingleDog([].shift.apply(arguments))}return instance
  }
})();
let dog1 = SingleDog.getInstance('Jack')
let dog2 = SingleDog.getInstance('Jack2')
console.log(dog1 === dog2)
Copy the code

It’s easy to see from the code that the key to implementing the singleton pattern is to determine whether the class has already instantiated an object. As for the specific code, there are many ways, like my closure here can be implemented, in addition to ES6 class can also be implemented. GetInstance = SingleDog; getInstance = SingleDog; getInstance = SingleDog; getInstance = SingleDog; And you can only get an object by singledog.getInstance. If we call the constructor directly with new, then SingleDog is no longer a singleton class.

Transparent singleton pattern

The “opacity” problem was mentioned above, but the solution is simply a deeper encapsulation of the entire SingleDog.

let SingleDog = (function () {
  let instance = null
  let SingleDog = function (name) {
    this.name = name
    if (instance) {
      return instance
    }
    return instance = this
  }
  return SingleDog
})()

console.log(new SingleDog() === new SingleDog())  // true
Copy the code

To encapsulate instance, self-executing functions and closures are used. The self-executing function returns the constructor for the real SingleDog instance, in which it determines whether it has been instantiated. Since it is a closure, the instnace variable will not be destroyed. This way, when you instantiate an object with this class, you don’t need to know in advance that it is a singleton, and it can be called just like a normal class through the new operator. Make this singleton class “transparent.”

Singletons in JavaScript

Both implementations of the singleton pattern are more likely to be implemented in traditional object-oriented languages, where singleton objects must be created from “classes,” which is a natural thing to do in class-centric languages. However, JavaScript is a class-free language, which means that it is not appropriate to apply design patterns directly to JavaScript. There are many ways to create objects in JavaScript that don’t necessarily require a constructor. Why create a “class” when all we need is a unique object? This is tantamount to taking off your pants to fart ———— superfluous.

Inert singleton

Lazy singletons are instances that are created only when they are needed. Laziness is a very important technical point in the singleton pattern. You’ve been using lazy singletons since the beginning of this article. It’s just a “class-based” singleton pattern, and class-based singleton patterns don’t work at all in JavaScript. Let’s say there is a modal box on the page. You can open the modal box by clicking a button. You don’t want to delete or add nodes frequently, but you can control the display and hide by changing the style. This is where lazy singletons come in.

.model{
  width: 300px;
  height: 150px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid black;
}
Copy the code
<button class="btn">Pop-up dialog box</button>
Copy the code
const createModel = (function (content) {
  let instance = null
  return function () {
    if (instance) {
      return instance
    }
    const div = document.createElement('div')
    div.className = 'model'
    div.innerHTML = arguments[0]
    div.style.display = 'none'
    document.body.appendChild(div)
    // Return a DOM node, not an object of a certain type
    // There is no need to create a "class"; // There is no need to create a "class";
    // This is why the "class-based" singleton pattern is not fully applicable in JavaScript
    return instance = div
  }
})()

const btn = document.querySelector('.btn')
btn.addEventListener('click'.function () {
  // The same DIV DOM node is used every time the button is clicked
  const model = createModel('I am a modal box')
  model.style.display = 'block'
})
Copy the code

Generic lazy singleton

In the previous code, we implemented a usable lazy singleton, but it had the following problems

  • This code clearly violates the “single responsibility principle” because the logic for creating objects and managing singletons is written therecreateModelIn the
  • If we need to create a unique page next timeiframeorscriptYou can’t do the same thing for cross-domain requestscreateModelCan I copy the function again?

So, we need to distinguish between what’s changing and what’s not. Isolate the invariant parts. In our case, regardless of the differences between creating an iframe or script, the logic for managing singletons is entirely separable. Now take the code for managing singletons out of the box and wrap it inside the getSingle function, passing the method fn that created the object as an argument

function getSingle(fn) {
  let result = null
  return function () {
    return result || (result = fn.apply(this.arguments))}}Copy the code

Next, pass getSingle the function used to create the modal box as fn, and then ask getSingle to return a new function that uses the result variable to hold the result of fn’s calculation. Because result is a closure, the result variable will not be destroyed. In future requests, Return the result variable if it has a value.

const createModel = function (content) {
  const div = document.createElement('div')
  div.className = 'model'
  div.innerHTML = arguments[0]
  div.style.display = 'none'
  document.body.appendChild(div)
  return instance = div
}

function getSingle(fn) {
  let result = null
  return function () {
    return result || (result = fn.apply(this.arguments))}}const singelModel = getSingle(createModel)
const btn = document.querySelector('.btn')
btn.addEventListener('click'.function () {
  const model = singelModel('I am a modal box')
  model.style.display = 'block'
})
Copy the code

In this example, we have separated the responsibility for creating the object and the logic for managing the singletons into two methods, so that the two methods can change independently of each other. When they’re connected, they’re creating a unique object, and they’re asking you, is that cool? This may be the unique charm of JavaScript. If we want to create some other singletons, just write the logic normally. How to manage the singletons directly to the getSingel function

conclusion

This article started with the basic idea of the singleton pattern, which is to determine whether a class has been instantiated. Since the getSingle method is not “transparent” in getting a unique object, we wrap the entire constructor, using self-executing functions and closures to return the real constructor. Laziness is one of the most important aspects of the singleton pattern: creating a unique object only when it is needed. To accommodate more situations we have separated the actual logic code from the logic that manages singletons, so that they can change independently without affecting each other. Improved code readability and maintainability.