1_单例模式
1 简介
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点
线程池、全局缓存、浏览器中的window对象
2 实现单例模式
- 思路:用一个变量来标志当前是否已为某个类创建过对象,是则在下一次获取该类的实例时直接返回之前创建的对象
function Singleton(name) {
this.name = name
}
Singleton.getInstance = (function() {
let instance = null
return function(name) {
if(!instance) {
instance = new Singleton(name)
}
return instance
}
})()
不透明性 -- 此时通过
Obj.getInstance
创建对象,但是一般是通过new Obj
创建实例对象的
透明的单例模式
- CreateDiv 单例类:负责在页面中创建唯一的 div 节点
let CreateDiv = (function() {
let instance;
class CreateDiv {
constructor(html) {
if(instance) {
return instance
}
this.html = html;
this.init();
// 将创建的实例对象赋值给instance
return instance = this
}
init() {
let 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 使用了自执行的匿名函数和闭包, 且匿名函数返回了真正的构造类 --> 复杂度增加
- 违反单一职责原则 -- 创建节点和管理单例的逻辑都放在 CreateDiv内部
3 用代理实现单例模式
1. 将负责管理单例的代码移出
使其成为普通的创建div的类
class CreateDiv {
constructor(html) {
this.html = html
this.init()
}
init() {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
}
2. 定义代理类 proxySingletonCreateDiv
var proxySingletonCreateDiv = (function() {
let instance
return function(html) {
if(!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
将负责管理单例的逻辑移到了代理类
proxySingletonCreateDiv
中。此时CreateDiv
就变成了一个普通的类,它跟proxySingletonCreateDiv
组合起来就可以达到单例模式的效果
3. 应用
var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
console.log(a === b)
4 惰性单例
在合适的时候才创建对象,并且只创建唯一一个
webqq登录浮窗
以webQQ的登录浮窗为例,介绍与全局变量结合实现惰性的单例
- 点击登录弹出登录浮窗时,该浮窗在页面里总是唯一的,不可能出现同时存在两个登录窗口的情况
1. 方案1:在页面加载时便创建好浮窗,初始隐藏,用户点击登录按钮时才显示
let loginLayer = (function() {
let div = document.createElement('div')
div.innerHTML = '我是登录浮窗'
div.style.display = 'none'
document.body.append(div)
return div
})()
document.getElementById('loginBtn').onclick = function() {
loginLayer.style.display = 'block'
}
2. 方案2:点击登录按钮时再创建浮窗
let 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() {
let loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
存在的问题
- 违反单一职责原则 -- 创建对象和管理单例的逻辑都放在
createLoginLayer
对象内部 - 无通用性
下次要创建页面唯一的script标签用来跨域请求数据,需要copy代码
5 通用的惰性单例
将不变的部分隔离出来 -- 管理单例的逻辑抽象出来:用一个变量来标志是否创建过对象,是则直接返回这个已经创建好的对象
1. 管理单例 -- getSingle()
创建对象的方法被当成参数动态传入getSingle函数
function getSingle(fn) {
let result
return function() {
return result || (result = fn.apply(this, arguments))
}
}
2. 创建对象 -- 创建浮窗的函数fn
function createLoginLayer() {
let div = document.createElement('div')
div.innerHTML = '我是登录浮窗'
div.style.display = 'none'
document.body.appendChild('div')
return div
}
3. 应用
let createSingleLoginLayer = getSingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function() {
let loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}