单例模式 ,保证一个类只有一个实例,并提供一个访问它的全局访问点。也就是说,第二次使用同一个类创建新对象的时候,应该得到与第一次创建的对象完全相同的对象

  • 浏览器中的 windowdocument 全局变量,这两个对象都是单例,任何时候访问他们都是一样的对象,

    • window 表示包含 DOM 文档的窗口,
    • document 是窗口中载入的 DOM 文档,分别提供了各自相关的方法
  • 在 ES6 新增语法的 Module 模块特性,通过 import/export 导出模块中的变量是单例的,也就是说,如果在某个地方改变了模块内部变量的值,别的地方再引用的这个值是改变之后的

  • 项目中的全局状态管理模式 Vuex、Redux、MobX 等维护的全局状态,vue-router、react-router 等维护的路由实例

  • localStorage的封装 ;
  • 全局静态模态框

如何对构造函数使用 new 操作符创建多个对象时,仅获取同一个单例对象呢?

单例模式的通用实现:

  1. Singleton :特定类,这是我们需要访问的类,访问者要拿到的是它的实例;
  2. instance :单例,是特定类的实例,特定类一般会提供 getInstance 方法来获取该单例;
  3. getInstance :获取单例的方法,或者直接由 new 操作符获取;

有几个实现点要关注一下:

  1. 访问时始终返回的是同一个实例
  2. 自行实例化,无论是一开始加载的时候就创建好,还是在第一次被访问时;
  3. 一般还会提供一个 getInstance 方法用来获取它的实例

单例模式 - 图1

IIFE 方式创建单例模式

使用立即调用函数 IIFE 将不希望公开的单例实例 instance 隐藏。

  1. const Singleton = (function() {
  2. let _instance = null // 存储单例
  3. const Singleton = function() {
  4. if (_instance) return _instance // 判断是否已有单例
  5. _instance = this
  6. this.init() // 初始化操作
  7. return _instance
  8. }
  9. Singleton.prototype.init = function() {
  10. this.foo = 'Singleton Pattern'
  11. }
  12. Singleton.getInstance = function() {
  13. if (_instance) return _instance
  14. _instance = new Singleton()
  15. return _instance
  16. }
  17. return Singleton
  18. })()
  19. const visitor1 = new Singleton()
  20. const visitor2 = new Singleton() // 既可以 new 获取单例
  21. const visitor3 = Singleton.getInstance() // 也可以 getInstance 获取单例
  22. console.log(v1 === v2, v1 === v3, v2 === v3); // true true true

单例模式赋能

将单例模式的创建逻辑和特定类的功能逻辑拆开,这样功能逻辑就可以和正常的类一样。

  1. /* 功能类 */
  2. class FuncClass {
  3. constructor(bar) {
  4. this.bar = bar
  5. this.init()
  6. }
  7. init() {
  8. this.foo = 'Singleton Pattern'
  9. }
  10. }
  11. /* 单例模式的赋能类 */
  12. const Singleton = (function() {
  13. let _instance = null // 存储单例
  14. const ProxySingleton = function(bar) {
  15. if (_instance) return _instance // 判断是否已有单例
  16. _instance = new FuncClass(bar)
  17. return _instance
  18. }
  19. ProxySingleton.getInstance = function(bar) {
  20. if (_instance) return _instance
  21. _instance = new Singleton(bar)
  22. return _instance
  23. }
  24. return ProxySingleton
  25. })()
  26. const visitor1 = new Singleton('单例1')
  27. const visitor2 = new Singleton('单例2')
  28. const visitor3 = Singleton.getInstance()
  29. console.log(visitor1 === visitor2) // true
  30. console.log(visitor1 === visitor3) // true

懒汉式-饿汉式

有时候一个实例化过程比较耗费性能的类,但是却一直用不到,如果一开始就对这个类进行实例化就显得有些浪费,那么这时我们就可以使用惰性创建(懒汉式)。
之前的例子都属于惰性单例,实例的创建都是 **new** 的时候才进行

懒汉式 <——**> **饿汉式

  • 懒汉式单例是在使用时才实例化
  • 饿汉式是当程序启动时或单例模式类一加载的时候就被创建。

    1. class FuncClass {
    2. constructor() { this.bar = 'bar' }
    3. }
    4. // 饿汉式
    5. const HungrySingleton = (function() {
    6. const _instance = new FuncClass()
    7. return function() {
    8. return _instance
    9. }
    10. })()
    11. // 懒汉式
    12. const LazySingleton = (function() {
    13. let _instance = null
    14. return function() {
    15. return _instance || (_instance = new FuncClass())
    16. }
    17. })()
    18. const visitor1 = new HungrySingleton()
    19. const visitor2 = new HungrySingleton()
    20. const visitor3 = new LazySingleton()
    21. const visitor4 = new LazySingleton()
    22. console.log(visitor1 === visitor2) // true
    23. console.log(visitor3 === visitor4) // true

    饿汉式在 HungrySingleton 这个 IIFE 执行的时候就进入到 FuncClass 的实例化流程了,
    懒汉式的 LazySingleton 中 FuncClass 的实例化过程是在第一次 new 的时候才进行的。

单例模式的优缺点

单例模式主要解决的问题就是节约资源,保持访问一致性
优点

  1. 单例模式在创建后在内存中只存在一个实例,节约了内存开支和实例化时的性能开支,特别是需要重复使用一个创建开销比较大的类时,比起实例不断地销毁和重新实例化,单例能节约更多资源,比如数据库连接;
  2. 只使用一个实例,也可以减小垃圾回收机制 GC(Garbage Collecation) 的压力,表现在浏览器中就是系统卡顿减少,操作更流畅,CPU 资源占用更少;

缺点:

  1. 单例模式对扩展不友好,一般不容易扩展,因为单例模式一般自行实例化,没有接口;