JS单例的最佳实践

102 阅读1分钟

期待的单例

import { CreateSingle } from './single.js'

const single1 = new CreateSingle()
const single2 = new CreateSingle()

console.log(single1 === single2) // 期待是true

期待是true,可惜是false.
如果CreateSingle是个普通的class类,那么new出来的对象都没关联.

不完善的单例1

class CreateSingle {
  constructor() {
    console.log('我创建了个单例')
  }
}

const single = new CreateSingle()

export { single }

这样外面引入了几次的single,constructor构造器只会执行一次,但是有个问题是,我们把生成单例的时机提前了,瑕疵.

不完善的单例2

在其他面向对象语言中如果想实现单例,只要把constructor设为private私有化,强制用户使用方法去获取单例即可:

class CreateSingle {
  private constructor() {
    console.log('我创建了个单例')
  }
  static _ins = null
  static getInstance() {
    if(!this._ins) {
      this._ins = new CreateSingle()
    } 
    return this._ins
  }
}

但是由于js中无法对构造器私有化,如果有人执行了new CreateSingle(),就打破了单例.不过受此启发,我们可以用一个函数利用闭包来生成个单例.

class _CreateSingle {
  constructor() {
    console.log('我创建了个单例')
  }
  // ...
}

function createSingleFn() {
  let _ins
  return class {
    constructor(...args) {
      if(!_ins) {
        _ins = new _CreateSingle(...args)
      }
      return _ins
    }
  }
}
const CreateSingle = createSingleFn()
export { CreateSingle }

这样new出来的两个对象都相等了,可惜还是有瑕疵:

import { CreateSingle } from './single.js'

const single = new CreateSingle()

CreateSingle.prototype.add = function () {}

single.add() // error报错  single下没有add

因为我们CreateSingle其实是函数内部返回的匿名class类,而我们的单例实则是_CreateSingle类,两者断开了联系无不相关.

利用代理解决实现单例

还是用上个例子的思路,但是把匿名class改为proxy,利用construct方法拦截new操作符.

// ...
function createSingleFn() {
  let _ins
  return new Proxy(_CreateSingle, {
    construct(target, args) {
      if(!_ins) {
        _ins = new target(...args)
      }
      return _ins
    }
  }) 
}

附上Proxy的MDN文档和其下的construct的MDN文档