单例模式介绍
单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等。
在JavaScript开发中,单例模式的用途非常广泛。
试想一下,当我们点击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,
无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
实现单例模式原理
要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。
不透明的单例
// 静态方法的方式
function singleton(name) {
this.name = name
this.instance = null
}
singleton.prototype.getName = function () {
console.log(this.name)
}
singleton.getInstance = function(name) {
if (!this.instance) {
this.instance = new singleton(name)
}
return this.instance
}
let a = singleton.getInstance('sven1')
let b = singleton.getInstance('sven2')
console.log(a === b) // true
或者
// 静态方法 + 闭包
let singleton = function (name) {
this.name = name
}
singleton.prototype.getName = function () {
console.log(this.name)
}
singleton.getInstance = (function () {
let instance = null
return function (name) {
if (!instance) {
instance = new singleton(name)
}
return instance
}
})()
let a = singleton.getInstance('sven1')
let b = singleton.getInstance('sven2')
console.log(a === b)
缺点:
1.不够"透明",无法使用new来进行类的实例化。
2.我们通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式相对简单,
但是有一个问题,就是增加了这个类的"不透明性",Singleton类的使用者必须知道这是一个单例类,
跟以往不通过new的方式来获取对象不同,这里偏要使用Singleton.getInstance来获取对象。
透明的单例模式
let CreateSington = (function () {
let instance;
return function (name) {
if (instance) {
return instance;
}
this.name = name;
return (instance = this);
};
})();
CreateSington.prototype.getName = function () {
console.log(this.name)
}
let Winner = new CreateSington('Winner')
let Looser = new CreateSington('Loower')
console.log(Winner === Looser) // true
console.log(Winner.getName()) // Winner
console.log(Looser.getName()) // Winner
透明的单例模式,意图统一使用
new操作符来获取单例对象,而不是 Singleton.getInstance()
用透明单例创建唯一节点
我们现在的目标是实现一个"透明"的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。 在下面的例子中,我们将使用 CreateDiv 单例类,它的作用是负责在页面中创建唯一的div节点。
var CreateDiv = (function () {
var instance
var CreateDiv = function (html) {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
CreateDiv.prototype.init = function () {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return CreateDiv
})()
var a = new CreateDiv('sven1')
var b = new CreateDiv('sven2')
console.log(a === b) // true
缺点:为了把instance封装起来,我们使用了自执行的匿名函数和闭包,让这个匿名函数返回真正的 Singleton 构造方法,这增加了一些程序的复杂程度,阅读起来也不是很舒服。
var CreateDiv = function (html) {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
// CreateDiv 构造函数实际上负责了两件事情。
// 1. 创建对象和执行初始化init方法,
// 2. 保证只有一个对象。虽然我们还没接触过 "单一职责原则" 的概念,但是可以明确的是,这是一种不好的做法。至少这个构造函数看起来很奇怪。
// PS: 假如某天我们需要用这个类,在页面中创建千千万万个的div,
// 既要让这个类从单例类变成一个普通的可产生多个实例的类。那么我们必须改下CreateDiv函数。
// 把控制创建唯一对象的那一段去掉。这种修改会给我们带来不必要的烦恼。
代理版的单例模式
通过 "代理"的形式:将 管理类操作 和 对象创建操作进行拆分,实现更小的力度划分,符合 "单一职责原理"。
// 将管理单例操作,与对象创建操作进行拆分,实现更小粒度划分,符合 "单一职责原则"
let ProxyCreateSingleton = (function () {
let instance
return function (name) {
// 代理函数仅管控单例
if (instance) {
return instance
}
return instance = new Singleton(name)
}
})()
// 独立的Singleton类,处理对象实例。
let Singleton = function (name) {
this.name = name
}
Singleton.prototype.getName = function () {
console.log(this.name)
}
let Winner = new ProxyCreateSingleton("Winner")
let Looser = new ProxyCreateSingleton('Looser')
console.log(Winner === Looser) // true
console.log(Winner.getName()) // Winner
console.log(Looser.getName()) // Winner
惰性单例模式
惰性单例指的是 在需要的时候才会创建对象。惰性单例是单例模式的重点,这种技术在实际开发中非常有用。
// 惰性单例:在需要的时候才创建对象实例:
function Singleton(fn) {
let result
return function () {
return result || (result = fn.apply(this, arguments))
}
}