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
- The above code violates the single responsibility principle, creating objects and managing singletons in each section of the getLoginLayer object
- 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