Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

Record yourself learning design patterns, content from

JavaScript Design Patterns and Development Practices

Definition of a singleton pattern

Ensure that a class has only one instance and provide a global access point to access it

The singleton pattern is a common pattern where we only need one object, such as the thread pool, the global cache, the window object in the browser, and so on. In JavaScript development, the singleton pattern is equally useful. Imagine that a pop-up window appears on the page when the single login button is used. This pop-up window is unique and will only be created once no matter how many times the single login button is used. Therefore, this login float window is suitable for singleton mode

Plain singleton pattern


const Singleton = function(name) {
    this.name = name
}

Singleton.instance = null

Singleton.prototype.getName = function() {
    alert(this.name)
}

Singleton.getInstance = function(name) {
    if (!this.instance) {
        this.instance = new Singleton(name)
    }
    return this.instance
}

const a = Singleton.getInstance('a')
const b = Singleton.getInstance('b')

console.log(a === b) // true

Copy the code

Although a singleton pattern has been written above, this singleton pattern code is not significant

Transparent singleton pattern

const CreateDiv = (function() {
    let instance;

    const CreateDiv = function(html) {
        if (instance) {
            return instance
        }
        this.html = html
        this.init()
        return instance = this
    }

    CreateDiv.prototype.init = function() {
        const div = document.createElement('div')
        div.innerHTML = this.html
        document.body.appendChild(div)
    }

    return CreateDiv

})()

const a = new CreateDiv('a')
const b = new CreateDiv('b')

console.log(a, b)
console.log(a === b)
Copy the code

Although a transparent singleton class has been written, it also has some disadvantages, including the use of self-executing anonymous functions and closures, which increase the complexity of the program and make reading uncomfortable

const CreateDiv = function(html) {
    if (instance) {
        return instance
    }
    this.html = html
    this.init()
    return instance = this
}
Copy the code

In this code, createDiv does two things. First, it creates the object and performs the init method. Second, it ensures that there is only one object, which does not comply with the single responsibility rule

If we someday needed to create thousands of divs from this class, we would have to rewrite the CreateDiv constructor, which would cause unnecessary annoyance

Implement the singleton pattern with a proxy

const CreateDiv = function(html) {
    this.html = html
    this.init()
}
CreateDiv.prototype.init = function() {
    const div = document.createElement('div')
    div.innerHTML = this.html
    document.body.appendChild(div)
}
Copy the code

Next, write the proxy class

const ProxySingletonCreateDiv = (function() {
    let instance;
    return function(html) {
        if(! instance) { instance =new CreateDiv('a')}return instance
    }
})()

const a = new ProxySingletonCreateDiv('a')
const b = new ProxySingletonCreateDiv('b')

console.log(a, b)
console.log(a === b)

Copy the code

Using the proxy class, we also write the singleton pattern. Instead, we move the logic responsible for managing the singleton into the proxy class. In this way, createDiv becomes a normal class, which is combined with the proxy to act as a singleton

A singleton in JavaScript

The previous implementations of the singleton pattern are more similar to those found in traditional object-oriented languages, where the singleton pattern is created from the “class”

Using namespaces

const MyApp = {}

MyApp.namespace = function(name) {
    const parts = name.split('. ')
    let current = MyApp
    for (let key of parts) {
        if(! current[key]) { current[key] = {} } current = current[key] } } myApp.namespace('event')
myApp.namespace('dom.style')

// The above code is equivalent to

var MyApp = {
    event: {},
    dom: {
        style: {}}}Copy the code

Use closures to encapsulate private variables

var user = (function() {
    const _name = 'jie',
        _age = '22';
    
    return {
        getUserInfo() {
            return _name + The '-' + _age
        }
    }
})()
Copy the code

Inert singleton

Lazy singletons, where object instances are created only when needed, can be more useful in real life development than we think

For example, create a unique popover

const getLoginLayer = (function() {
    let div;
    return function() {
        if(! div) { div =document.createElement('div')
            div.style.display = 'none'
            div.innerHTML = 'I'm a popover'
            div.classList.add('popup')
            document.body.appendChild(div)
        }
        return div
    }
})()

const open = document.getElementById('open')

const close = document.getElementById('close')

open.onclick = function() {
    console.log('I clicked')
    const loginLayer = getLoginLayer()
    loginLayer.style.display = 'block'
}
close.onclick = function() {
    const loginLayer = getLoginLayer()
    loginLayer.style.display = 'none'
}
Copy the code

The div will only be created once

Generic lazy singleton

  1. The above code violates the single responsibility principle, creating objects and managing singletons in each section of the getLoginLayer object
  2. Next time we need to create a unique iframe, or script tag, on the page, we’ll do the same
const getIframe = (function() {
    let iframe;
    return function() {
        if(! iframe) { iframe =document.createElement('iframe')
            iframe.style.display = 'none'
            document.body.appendChild(iframe)
        }
        return iframe
    }
})()
Copy the code

Now let’s pull the logic out of this creation

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

We pass in the method of creating the function fn argument to getSingle, so that we can create not only loginLayer, but also script, iframe, and then let getSingle return a new function that holds the result returned by FN in a variable. Result is in the closure and will never be destroyed.

const getSingle = function(fn) {
    let result;
    return function() {
        return result || (result = fn.apply(this.arguments))}}const getLoginLayer = function() {
    const div = document.createElement('div')
    div.style.display = 'none'
    div.innerHTML = 'I'm a popover'
    div.classList.add('popup')
    document.body.appendChild(div)
    return div
}

const createSingleLoginLayer = getSingle(getLoginLayer)

open.onclick = function() {
    console.log('I clicked')
    const loginLayer = createSingleLoginLayer()
    loginLayer.style.display = 'block'
}
close.onclick = function() {
    const loginLayer = createSingleLoginLayer()
    loginLayer.style.display = 'none'
}
Copy the code

Let’s create a unique iframe to dynamically load third-party pages

open.onclick = function() {
    const iframe = createSingleIframe()
    iframe.style.display = 'block'
    iframe.src = 'https://www.bilibili.com/'
}
close.onclick = function() {
    const iframe = createSingleIframe()
    iframe.style.display = 'none'
}
Copy the code

Singletons can be used for more than just object creation. For example, when we render a list on a page, we bind the list with a Click event. If we add the list dynamically through Ajax, using an event broker, the click event actually only needs to be bound once on the first rendering. However, we don’t want to determine if this is the first time to render the list. With Jquery, we usually bind the node with one event.

var bindEvent = function() {$('div').one('click'.function() {
        alert('click) }) } var render = function() { bindEvent() } render() render() render()Copy the code

The same effect can be achieved by using the getSingle function. The following code

var bindEvent = getSingle(function() {
    console.log('I executed internally.')
    document.getElementById('div1').addEventListener('click'.function() {
        alert('click')})return true
})

var render = function() {
    console.log('I did it.')
    bindEvent()
}

render()
render()
render()
Copy the code

As you can see, the Render function and bindEvent are executed three times each, but div is actually bound to only one event