前端开发设计模式: 单例模式(Singleton Pattern)

什么是单例模式?(Singleton Pattern) 

单例模式,也叫单体模式,是一种创建型设计模式,是全局(或某一作用域范围)唯一实例,大家共享、复用一个实例对象。—— 最基础、最常见的设计模式

1、保证对象实例只创建一次,后续的引用都是同一个实例对象
2、保证一个类只有一个实例,并提供一个访问它的 全局访问点

 

单例模式特点:

  • 唯一性只有一个实例,无论在程序的任何地方访问这个类,都将得到同一个实例
  • 全局访问:提供了一种全局访问该实例的方式,使得在整个程序中都可以方便地使用这个唯一的实例

 

为什么要用单例模式?

单例模式的优点

  1、减少资源消耗

    对于一些需要频繁创建但又只需一个实例的对象,如全局状态管理、日志记录器等,使用单例模式可以避免重复创建对象带来的资源浪费和性能问题

  2、全局访问

    提供了一个简单的方式来访问唯一的实例,方便在不同的模块中使用

  3、易于管理

    由于只有一个实例,对于一些需要统一管理的对象,如配置对象、全局缓存等,使用单例模式可以方便地进行管理和维护。

 

单例模式的缺点

  1、测试困难

    由于单例通常是全局可访问的,这使得在单元测试中难以模拟和控制其行为,可能会导致测试的复杂性增加

  2、违法单一职责原则

    单例对象可能承担过多的职责,不利于代码的可维护性和扩展性

  3、可能导致内存泄漏

    如果单例对象在整个应用的生命周期中都存在,并且持有一些资源(如数据库连接、文件句柄等),如果不及时释放这些资源,可能会导致内存泄漏

 

单例模式的应用场景

  1、全局状态管理

    在前端应用中,可能需要一个全局的状态管理器来存储和管理应用的状态。使用单例模式可以确保只有一个状态管理器实例,避免状态的混乱和不一致

  2、日志记录器

    日志记录器通常需要在整个应用中共享,以便在不同的地方记录日志。使用单例模式可以确保只有一个日志记录器实例,方便进行日志的统一管理和输出。

    例如,可以创建一个单例的日志记录器对象,提供方法来记录不同级别的日志信息,并将日志输出到控制台或文件中。

  3、数据库连接

    在与数据库进行交互时,通常需要建立数据库连接。使用单例模式可以确保只有一个数据库连接实例,避免重复建立连接带来的资源浪费和性能问题。

    例如,可以创建一个单例的数据库连接对象,提供方法来执行数据库查询和更新操作,并在应用启动时建立连接,在应用关闭时关闭连接。

    常见的场景就是:web 实时通信 SSE连接

 

如何实现单例模式?

 单例模式实现有多种方式,如下:

一、全局对象(不推荐)

  两种实现方式:

  1. 在全局环境中用 var 字面量声明一个对象,利用 var 的变量提升 + 全局属性的特点(全局环境下的 var 变量会自动成为全局属性),所以慎用 var
  2. 直接挂载到 全局对象 window 上

  优点:

    使用简单

  缺点:

    会存在全局污染

 

  实例如下:

// 例1:
window.jQuery = window.$ = jQuery;

// 例2:
window._ = lodash

// 例3:
// 全局环境下的 var 变量会自动成为全局属性
var singleUser = {
    name: 'sam',
    id: 1001
}
//使用
console.log(singleUser.name) // sam
console.log(window.singleUser.name) // sam

 

二、立即执行函数  +  闭包实现

思路:利用 JS 的闭包 来保存那个唯一对象实例,这样就可以 通过 new 来获取唯一实例对象。

 

实例如下:

const GlobalUser = (function () {
    let instance  // 闭包保存的唯一实例对象

    function createInstance(obj){
        return obj
    }

    return function(obj) {
        if(!instance){
            instance = createInstance(obj)
        }
        
        return instance
    }
})()  // 立即执行,外层函数的价值就是他的闭包变量 instance

console.log(new GlobalUser({name: 'sam', id: '1001'}).name) // sam

// 依然是 sam,复用了第一次创建的实例
console.log(new GlobalUser({name: 'baby', id: '1002'}).name) // sam

console.log(new GlobalUser() === new GlobalUser()) // true

 

三、ES6 实现

  1、ES6 类

class Singleton {
    constructor(){
        if(!Singleton.instance){
            Singleton.instance = this
        }
        return Singleton.instance;
    }
}

 

  2、ES6 模块 Module 

  ES6 的模块其实就是单例模式,模块中导出的对象就是单例的,多次导入其实是同一个引用。

  • Singleton 模式:import 模块的代码只会执行一次,同一个 url 文件只会第一次导入时执行代码。后续任何地方 import 都不会执行模块代码了,也就是说,import 语句是 Singleton 模式的。
  • 只读 - 共享:模块导入的接口的是只读的,不能修改。当然引用对象的属性值是可以修改的,不建议这么干,注意模块是共享的,导出的是一个引用,修改后其他方也会生效。

因此用 ESM 实现单例就比较简单了。

// 模块声明 config.js
export default {
    title: '设计模式'
}
// 使用
import config from './config.js'
console.log(config)  // {title: '设计模式'}
config.title = '修改一下'

import config2 from './config.js'
console.log(config, config2)  // {title: '修改一下'}  {title: '修改一下'}

 

 

单例模式的注意事项

  1、线程安全

    在多线程环境下,需要确保单例的创建是线程安全的。可以使用锁或其他同步机制来保证在多个线程同时访问单例时,只有一个实例被创建

  2、延迟加载

    可以考虑使用延迟加载的方式来创建单例实例,即在第一次访问单例时才创建实例。这样可以避免在应用启动时就创建一些可能不需要的对象,提高应用的启动速度。

  3、可扩展性

    在设计单例类时,要考虑到未来可能的扩展需求。尽量保持单例类的接口简洁和可扩展,以便在需要时可以方便地添加新的功能。

 

posted on 2024-10-30 18:48  bala001  阅读(18)  评论(0编辑  收藏  举报

导航