设计模式-JS中的单例模式
背景
此前项目有个 im 模块,需要用到 websocket。使用 websocket 需要建立连接:
const initWs = () => {
const ws = new WebSocket('wss://xxx.com');
ws.onopen(() => {
console.log('opened')
})
ws.onmessage((evt) => {
console.log('Received evt', evt.data)
})
//....
return ws
}
我们肯定不希望在每个用到 ws 的地方都重复初始化,即我们只需要一个 WebSocket 的实例。
const wsA = new SingleWs()
const wsB = new SingleWs()
// 有没有办法使得:
wsA instanceof WebSocket && wsB instanceof WebSocket && wsA === wsB
那么要如何做到这一点?实际上就可以用到单例模式。下面列举几个实现的方法:
使用全局变量
这个可能是最容易想到的:
window.ws = window.ws || initWs()
将 ws 挂在 window 对象上,使用时也用 window.ws,显然这个解决方案有个问题是需要小心 window.ws 被覆盖。
闭包
闭包可以保持局部变量保持在内存中不被销毁,通过这个特性可以就可以避免污染全局变量,如下:
const SingleWs = (function() {
let ws = null;
// const initWs = ....
return {
getWs() {
if(!ws) {
ws = initWs()
}
return ws
}
}
})()
const wsA = SingleWs.getWs()
const wsB = SingleWs.getWs()
console.log(wsA instanceof WebSocket && wsB instanceof WebSocket && wsA === wsB)
然而上面的写法也有一些缺点,如 getWs 可以被改写。
写法还可以进一步改写:
const SingleWs = (function() {
let ws = null;
// const initWs = ....
return () => {
if(!ws) {
ws = initWs()
}
return ws
}
})()
// 这里的 new 可以不写
const wsA = new SingleWs()
const wsB = new SingleWs()
wsA instanceof WebSocket && wsB instanceof WebSocket && wsA === wsB
模块化
通过模块化也能实现上述的效果:
// ws.js
let ws = null
// const initWs = ....
function SingleWs() {
if (ws) {
return ws
}
ws = initWs()
return ws
}
export default SingleWs
通过 class 的静态属性来实现
class SingleWs {
constructor() {
if (!SingleWs.instance) {
SingleWs.instance = this.initWs()
}
return SingleWs.instance
}
initWs() {
// ...
}
}
const wsA = new SingleWs()
const wsB = new SingleWs()
console.log(wsA instanceof WebSocket && wsB instanceof WebSocket && wsA === wsB)
总结
JS 中的单例模式:
- 实例化时,判断是否已经存在实例。
- 如果不存在,则初始化,并将生成的实例存储起来。
- 存储的方法可以是全局变量,闭包,模块以及类的静态属性。
- 如果已经存在实例,那么就直接返回该实例。