设计模式-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 中的单例模式:

  1. 实例化时,判断是否已经存在实例。
  2. 如果不存在,则初始化,并将生成的实例存储起来。
  3. 存储的方法可以是全局变量,闭包,模块以及类的静态属性。
  4. 如果已经存在实例,那么就直接返回该实例。