1. 单例模式的简单理解
介绍:保证一个类仅有一个实例,并提供一个访问他的全局访问点
实现一个标准的单例模式:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
使用场景:当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论点击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就可以使用单例模式来实现
注意:全局变量不是单例模式!!! 全局变量存在很多的问题,容易造成命名空间的污染,解决方法
- 使用命名空间
- 使用闭包封装私有变量
2. 实现单例模式
let Singleton = function(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
}
const a = Singleton.getInstance('test1')
const b = Singleton.getInstance('test2')
console.log(a === b)
通过Singleton.getInstance 来获取Singleton类的唯一对象,这种方法相对简单,但是有一个问题,就是增加了这个类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,跟以往通过new XXX的方式获取对象不同,这里偏要使用Singleton.getInstance来获取对象
3. 透明的单例模式
let CreateDiv = (function() {
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
})()
const diva = new CreateDiv('div1')
const divb = new CreateDiv('div2')
console.log(diva, diva === divb)
缺点 为了把instance封装起来,使用了自执行函数和闭包,并且让匿名函数返回真正的Singleton构造方法,增加了程序的复杂度,阅读起来也不舒服
CreateDiv 的构造函数做了两件事情
- 创建对象和执行初始化init方法
- 保证只有一个对象
4. 用代理实现单例模式
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)
}
const ProxySingletonCreateDiv = (function() {
let instance = null
return function(html) {
if(!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
const proxy1 = new ProxySingletonCreateDiv('test1')
const proxy2 = new ProxySingletonCreateDiv('test2')
console.log(proxy1 === proxy2)
5. 惰性单例
惰性单例指的是在需要的时候才创建对象实例
5.1 通用的惰性单例
const createLoginLayer = (function() {
let div
return function() {
if(!div) {
div = document.createElement('div')
div.innerHTML = '登录弹窗'
div.style.display = 'none'
document.body.appendChild(div)
}
return div
}
})()
document.getElementById('loginBtn').onclick = function() {
var loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
存在的问题
- 违反单一职责原则,创建对象和管理单例逻辑都放在createLoginLayer对象内部
- 如果下次需要创建页面中唯一的iframe,或者script标签,用来跨域请求数据,就必须得如法炮制,把createLoginLayer函数几乎照抄一遍
5.2 优化后
const getSingle = function(fn) {
let result
return function() {
return result || (result = fn.apply(this, arguments))
}
}
const createLoginLayer = function() {
let div = document.createElement('div')
div.innerHTML = '登录弹窗'
div.style.display = 'none'
document.body.appendChild(div)
return div
}
const createSingleLoginLayger = getSingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function() {
var loginLayer = createSingleLoginLayger()
loginLayer.style.display = 'block'
}
在这个例子中,我们把创建实例对象的职责和管理单例的职责分别放置在两个方法里面,这两个方法可以独立变化而互不影响,当她们连接在一起的时候,就完成了创建唯一实例对象的功能
6. 总结
单例模式是一种特别简答但是非常实用的模式,特别是惰性单例模式,在适合的时候才创建对象,并且只创建唯一一个,更主要的是,创建对象和管理单例的职责分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威立
|