一、什么是单例模式
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式的实现原理是:用一个变量来标识当前类是否已经创建了实例,如果创建了就直接返回该实例,如果没有创建就创建新的实例。
二、实现单例模式
1.简单的单例模式
let Singleton = function (name) {
this.name = name
}
Singleton.prototype.say = function () {
console.log(this.name)
}
Singleton.getInstance = (function () {
let instance = null
return function (name) {
if (!instance) {
return new Singleton(name)
}
return instance
}
})()
let s1 = Singleton.getInstance('jack')
let s2 = Singleton.getInstance('rose')
console.log(s1 === s1) // true
这里我们通过Singleton.getInstance
来获取Singleton
的唯一对象,但是这种获取对象的方式有些别扭,与传统的new Singleton
有些区别,这样就增加了这个类的“不透明性”,即使用必须知道这是一个单例模式,需要通过Singleton.getInstance
来获取Singleton
的唯一对象。
2.透明的单例模式
let CreateDiv = (function (params) {
let instance
let CreateDiv = function (html) {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
CreateDiv.prototype.init = function () {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return CreateDiv
})()
let a = new CreateDiv('a')
let b = new CreateDiv('b')
console.log(a==b) // true
这样我们就创建了一个透明的单例模式,但是构造函数CreatedDiv
看起来有些奇怪,它调用了init
方法并保证了只创建一个对象,所以这样不符合单一职责原则。
所以我们引入代理类:
let CreateDiv = function (html) {
this.html = html
this.init()
}
CreateDiv.prototype.init = function () {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
let ProxySingle = (function () {
let instance
return function (html) {
if (!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
let a = ProxySingle('a')
let b = ProxySingle('b')
console.log(a===b) // true
这样我们把负责管理单例的逻辑移到了代理类ProxySingle
上,这样CreateDiv
类就只负责创建元素,变成了一个普通的类。
3、JavaScript的单例模式
javascript
是一门没有类的语言,所以在我们创建一个唯一的对象的时候,不需要像传统的语言一样先声明一个类。
在javascript
中虽然全局变量不是单例模式,但是在实际开发中我们经常会把全局变量当作单例模式来使用。因为当我们在全局变量下创建一个对象确实是独一无二的,并且该全局变量提供给全局访问,满足了确保只有一个实例,并提供全局访问的核心思想。
但是全局变量确实也存在诸多的问题,比如在大型项目中全局变量过多往往会造成命名空间的污染。所以我们应该避免使用过多的全局变量,我们可以使用命名空间和闭包的来解决全局变量污染的问题。
3.1、惰性单例
惰性单例是指在需要的时候才创建对象实例。比如我们前面的instance实例总是在调用Singleton.getInstance
才被创建。
let createLoginLayer = (function () {
let div
return function () {
if(!div) {
div = document.createElement('div')
}
return div
}
})()
document.getElementById('btn').onclick = function () {
let loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
3.1、通用的惰性单例
上面的例子任然违反了单一职责模式,因为创建对象和管理对象单例逻辑的代码都放在createLoginLayer
中,而却代码的复用性不强,如果我们需要创建的不是一个div
而是其他东西就需要把一样的代码重新写一遍。所以我们需要把创建对象和管理单例逻辑的代码分开。
首先我们创建一个管理单例的函数:
let getSingle = function (fn) {
let res
return function () {
return res || (res = fn.apply(this, arguments))
}
}
接下来我们将创建对象的函数作为参数传入getSingle
中,之后我们返回一个函数,并且用一个变量res
将fn
的返回结果保存起来。因为res
在自身的变量中,所以永远不会被销毁。在之后的请求中如果res
已经被赋值则直接返回该值。
使用:
// 创建DIV
let createLoginLayer = function () {
let div = document.createElement('div')
return div
}
let createSingleLoginLayer = getSingle(createLoginLayer)
document.getElementById('btn').onclick = function () {
let loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
// 创建script
let createScript = function () {
let script = document.createElement('script')
return script
}
let createSingleScript = getSingle(createScript)
document.getElementById('btn1').onclick = function () {
let scriptDom = createSingleScript()
doument.body.appenChild(scriptDom)
}
总结
单例模式是一种简单但非常实用的模式,特别是惰性单例,只在需要的时候才创建对像,并且只创建唯一的一个。更奇妙的是,创建对象和管理单例逻辑的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力,提高的代码的复用性。