JS中的单例模式(ES5/ES6/Nodejs)

8,568 阅读4分钟

原文博客地址,欢迎讨论,star

偶然间看到有人使用ES6class语法实现了一个比较好的单例模式,就想着结合所接触到的和网上一些讨论的实际例子来看看在javascript中单例是怎么玩耍的,怎么应用的。

ES3/ES5 中的单例模式

ES3/ES5中,还没有class这样的语法,之前最早接触设计模式的时候,一般网上的教程都是以java来解释的,因为作为面向对象的语言确实好像能直观的去解释这些设计模式。 比如,单例模式,就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

javascript中,是没有这个东西的,全都是对象,所以在其中实现单例一般是使用一个子执行函数返回一个对象,这个对象去有个方法去获取instance, 在获取的时候进行是否存在的判断。如下:

var Single = (function(){
  var instance;
  /*
  * 这里面还可以定义一些私有的方法,主要是用到了闭包 
  */
  function get() {
    /*
    * 这里返回的就是最后被使用到的对象 
    */
    return {
      doSomething: function () {
        console.log("AAA")
      }
    }
  }

  return {
    getInstance () {
      return instance || (instance = get()) // 如果instance 变量是有值的就直接返回,如果是没有值的就调用生成对象返回并赋值给instance
    } 
  }
})()

var instance1 = Single.getInstance()
var instance2 = Single.getInstance()
console.log(instance1 === instance2) // true

ES3/ES5一般都会使用如上组织形式去实现单例模式,接下来我们看一下在ES6之后的场景。

ES6 之后使用 class 语法糖实现

ES6引入class关键字,一套很简洁用来实现js里面构造函数的语法糖。如果对class的用法还不是很熟悉的可以点击阮老师的es6入门 来学习一下。先看代码:

const single =  'single' // 这里使用symbol会好一点
class A {
  static get instace () {
    if (this[single]) { // 由于是静态函数,这里的this指的是A,并不是 new A() 产生的对象哦。
      return this.single
    }
    return this[single] = new this() // 如果没有值就new 构造函数
  }

  constructor() {
    const sourceClass = this.constructor // 获取构造函数对象
    if (!sourceClass[single]) { // 判断对象上面是否已经有了单例
      sourceClass[single] = this // 这里的this指的是已经构造好的对象,空对象,只是constructor指向A
    }
    return sourceClass[single] // 如果已经存在则直接返回
  }
}

上面这份代码还是需要点较深ES6的知识才能看明白的,这个”类“里面有两个方法来产生实例。

  • 第一种就是构造函数了,会判断返回一个实例
  • 第二个就是有一个静态属性也会经过判断返回一个实例。 里面使用了this来指向A这个构造函数(构造函数也是对象),我代码上也做了注释。关键点就是,根据指定key来挂载这个实例,但是封装性更好了一点。

利用 nodejs 模块化实现单例子

其实这里说实现不是很准确,只能说目前nodejsES6的模块化中的每个模块其实就是单例的。下面我们来先看一下代码:

class A {
  doSomething() { ... }
}
modules.export = new A() // 这里直接将对象生成,然后导出

因为nodejsES6中每个模块的代码只会被加载一遍,第二次或者第三次等等,就会从cache里面去查找,是否已经加载过,就直接使用了。

  • 首先我们创建一个a.js,在里面随便打印一句话。
    a.js
  • 打开node,导入这个文件。
    require
  • 我们查看一下module
    module
    可以看到这个模块已经被存储下来了,之后回去就会根据id(模块的路径)判断,是否需要执行该模块的代码。

一些应用场景

  • 网页上的loading,整个网页可以只创建一个实例,根据传入不同的参数去渲染不同的样式或者行为,全局通用。
  • nodejs里面的数据库链接实例,访问一次创建一次链接是很消耗资源的,所以也是全局的查询操作都是统一使用一个实例。

总结

单例模式还是非常简单,也很实用。目前还可以实用ES6的代理去修改对象的创建来达到单例。这些只是实现方法,我们需要多多思考,那些场景这些设计模式来达到代码的可扩展性和可复用性等。