单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点
有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等
1const Singleton = function (name) {2 this.name = name3 // this.instance = null4}56Singleton.prototype.getName = function () {7 return this.name8}910// 通过标识 Singleton.instance 属性11Singleton.getInstance = function (name) {12 console.log(this) // this 指向 Singleton 构造函数13 if (!this.instance) {14 this.instance = new Singleton(name)15 }16 return this.instance17}1819// 通过闭包词法作用域标识20// Singleton.getInstance = (function () {21// const instance = null22// return function (name) {23// if (!instance) {24// instance = new Singleton(name)25// }26// return instance27// }28// })()2930const a = Singleton.getInstance('hah')31const b = Singleton.getInstance('aha')3233console.log(a === b)34console.log(a, b)
只能通过 Singleton.getInstance 来获取,不能通过 new,增加“不透明性”
透明的单例模式
1const CreateDiv = (function () {2 let instance34 const CreateDiv = function (html) {5 // 1. 保证只有一个 instance6 if (instance) {7 return instance8 }9 // 2. 创建对象和初始化10 this.html = html11 this.init()12 return instance = this13 }1415 CreateDiv.prototype.init = function () {16 const div = document.createElement('div)17 div.innerHTML = this.html18 document.body.appendChild(div)19 }2021 return CreateDiv22})()2324const a = new CreateDiv('<div>A</div>')25const b = new CreateDiv('<div>B</div>')26console.log(a === b)
使用闭包和自执行函数,阅读不方便
CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始化init方法,第二是保证只有一个对象。不符合“单一职责原则”
代理实现
对 CreateDiv 进行解藕
1// 创建 div2const CreateDiv = function (html) {3 this.html = html4 this.init()5}67CreateDiv.prototype.init = function () {8 const div = document.createElement('div)9 div.innerHTML = this.html10 document.body.appendChild(div)11}1213// 保证只有一个 instance 的代理类14const ProxySingletonCreateDiv = (function () {15 let instance1617 return function (html) {18 if (!instance) {19 instance = new CreateDiv(html)20 }21 return instance22 }23})()2425const a = new ProxySingletonCreateDiv('<div>A</div>')26const b = new ProxySingletonCreateDiv('<div>B</div>')27console.log(a === b)
JavaScript 中的单例模式
前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从“类”中创建而来。在以类为中心的语言中,这是很自然的做法
但JavaScript其实是一门无类(class-free)语言,也正因为如此,生搬单例模式的概念并无意义。在JavaScript中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?
可通过命名空间和闭包防止全局变量的污染来创建单例
惰性单例
instance 实例对象总是在我们调用Singleton.getInstance 的时候才被创建,而不是在页面加载好的时候就创建
页面登录弹窗:
1var createLoginLayer = (function(){2 var div;3 return function(){4 if (!div){5 div = document.createElement( 'div' );6 div.innerHTML = '我是登录浮窗';7 div.style.display = 'none';8 document.body.appendChild( div );9 }1011 return div;12 }13})()1415document.getElementById('loginBtn').onclick = function(){16 var loginLayer = createLoginLayer();17 loginLayer.style.display = 'block';18}
与第一段代码一样,违反单一职责原则,不能通用于其他标签
通用的惰性单例
先对逻辑进行抽象
抽离管理单例,封装在 getSingle 中,创建对象的具体方法 fn 作为参数传入
1const getSingle = function (fn) {2 const instance3 return function (...args) {4 if (!instance) {5 instance = fn(...args)6 }7 return instance8 }9}
进一步简化
1const getSingle = function (fn) {2 let result3 return function (...args) {4 return result || (result = fn(...args))5 }6}
页面弹窗:
1const createPopup = function (html) {2 const div = document.createElement('div')3 div.innerHTML = html4 div.style.display = 'none'5 document.body.appendChild(div)6 return div7}89const createSinglePopup = getSingle(createPopup)1011document.querySelector('#pupup-btn').addEventListener('click', () => {12 const popupLayer = createSinglePopup('<div>This is a popup...</div>')13 popupLayer.style.display = 'block'14})
这种单例模式的用途远不止创建对象
比如页面中一个列表,渲染完后添加事件委托,之后动态获取数据并最加渲染 li,click 事件只需第一次绑定就好
使用 jQ 的 one:
1var bindEvent = function(){2 $( 'div' ).one( 'click', function(){3 alert ( 'click' );4 });5};67var render = function(){8 console.log( '开始渲染列表' );9 bindEvent();10 // ...11}1213render()14render()
使用单例:
1var bindEvent = getSingle(function(){2 document.getElementById( 'div1' ).onclick = function(){3 alert ( 'click' );4 }5 return true;6});78var render = function(){9 console.log( '开始渲染列表' );10 bindEvent();11 // ...12};1314render();15render()
(这个例子有更简单方便的实现,这里只是一个例子)