JavaScript设计模式 -- 单例模式

embedded/2025/2/13 19:19:40/

在实际开发中,我们常常需要确保某个类只有一个实例,并提供全局访问点。**单例模式(Singleton Pattern)**正是为了解决这个问题而产生的。本文将详细介绍单例模式的原理、实现方式以及在 JavaScript 中的多种应用场景,通过多个示例代码,帮助你掌握如何在项目中使用单例模式。 

单例模式简介

单例模式的核心思想是保证一个类只有一个实例存在,并提供一个全局访问点来获取该实例。这样可以防止重复创建对象,节省资源,同时也便于全局共享数据或状态。

单例模式的特点:

  • 唯一性:在系统中只有一个实例存在。
  • 全局访问:提供一个全局访问点,让其他对象能够轻松地获取这个实例。
  • 延迟实例化:通常采用懒加载方式,只有在第一次使用时才创建实例。

JavaScript 中单例模式的实现方式

在 JavaScript 中,由于语言特性灵活,单例模式可以通过多种方式实现。下面介绍三种常见的实现方式。

基于对象字面量实现

最简单的单例实现方式就是使用对象字面量。适用于简单场景,不需要额外的初始化逻辑。

javascript">// 直接定义一个单例对象
const Singleton = {property: '这是一个单例属性',method() {console.log('调用单例方法');}
};// 客户端调用
Singleton.method(); // 输出:调用单例方法
console.log(Singleton.property); // 输出:这是一个单例属性

基于闭包实现

利用闭包和立即执行函数表达式(IIFE),可以创建一个带有私有变量的单例实例,并实现延迟加载。

javascript">const Singleton = (function() {let instance; // 用于保存单例实例// 私有初始化函数function init() {// 私有变量和方法let privateData = '这是私有数据';function privateMethod() {console.log('调用私有方法');}return {// 公有方法和属性getPrivateData() {return privateData;},publicMethod() {console.log('调用公有方法');privateMethod();}};}return {// 获取单例实例的接口getInstance() {if (!instance) {instance = init();}return instance;}};
})();// 客户端调用
const singletonA = Singleton.getInstance();
const singletonB = Singleton.getInstance();
console.log(singletonA === singletonB); // 输出:true
singletonA.publicMethod();
// 输出:
// 调用公有方法
// 调用私有方法

基于 ES6 类实现

利用 ES6 的 class 语法,可以在构造函数中判断是否已存在实例,从而实现单例模式。这种方式更符合面向对象的编程习惯。

 
javascript">class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}// 初始化操作this.timestamp = new Date();Singleton.instance = this;}getTime() {return this.timestamp;}
}// 客户端调用
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // 输出:true
console.log(instance1.getTime()); // 输出:实例创建时间


单例模式的应用场景

单例模式适用于那些需要全局唯一实例的场景,下面我们列举几个常见的例子。

配置管理器

在大型应用中,通常会有一套全局配置。使用单例模式可以确保配置管理器只有一个实例,方便统一管理和访问配置项。

javascript">class ConfigManager {constructor() {if (ConfigManager.instance) {return ConfigManager.instance;}this.config = {apiUrl: 'https://api.example.com',timeout: 5000};ConfigManager.instance = this;}get(key) {return this.config[key];}set(key, value) {this.config[key] = value;}
}// 客户端调用
const config1 = new ConfigManager();
const config2 = new ConfigManager();
console.log(config1 === config2); // 输出:true
console.log(config1.get('apiUrl')); // 输出: https://api.example.com

日志记录器

日志记录器通常需要在整个应用中保持唯一实例,这样可以统一管理日志记录的逻辑、格式以及输出目的地。

javascript">class Logger {constructor() {if (Logger.instance) {return Logger.instance;}Logger.instance = this;}log(message) {console.log(`[LOG] ${new Date().toISOString()}: ${message}`);}
}// 客户端调用
const logger1 = new Logger();
const logger2 = new Logger();
logger1.log('系统启动'); // 输出带有时间戳的日志信息
console.log(logger1 === logger2); // 输出:true

全局事件总线

在前端项目中,全局事件总线用于组件间通信。采用单例模式可以保证整个应用中只有一个事件总线,从而避免事件冲突和重复创建问题。

javascript">class EventBus {constructor() {if (EventBus.instance) {return EventBus.instance;}this.events = {};EventBus.instance = this;}on(event, callback) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(callback);}emit(event, ...args) {if (this.events[event]) {this.events[event].forEach(fn => fn(...args));}}
}// 客户端调用
const eventBus1 = new EventBus();
const eventBus2 = new EventBus();
eventBus1.on('test', data => console.log('事件接收:', data));
eventBus2.emit('test', 'Hello, Singleton!'); // 输出:事件接收: Hello, Singleton!
console.log(eventBus1 === eventBus2); // 输出:true

数据库连接

在 Node.js 应用中,数据库连接通常资源昂贵且需要复用。使用单例模式可以确保数据库连接对象全局唯一,避免重复建立连接。

javascript">class Database {constructor() {if (Database.instance) {return Database.instance;}// 模拟数据库连接建立this.connection = this.connect();Database.instance = this;}connect() {// 模拟一个数据库连接console.log('建立数据库连接...');return { /* connection object */ };}getConnection() {return this.connection;}
}// 客户端调用
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // 输出:true
db1.getConnection(); // 已建立的数据库连接

单例模式的优缺点

优点

  • 资源共享:确保系统中某个对象只有一个实例,便于共享资源(如配置、连接等)。
  • 全局访问:提供全局访问点,方便在不同模块中调用。
  • 避免重复实例化:节省资源,避免频繁创建和销毁对象。

缺点

  • 隐藏依赖单例模式可能导致类之间隐式的依赖关系,不利于测试和扩展。
  • 并发问题:在多线程环境下需要额外处理线程安全问题(在 JavaScript 单线程环境中较少见)。
  • 难以继承:单例的实现可能会限制类的扩展性和灵活性。

总结

单例模式是一种简单但十分有用的设计模式,通过确保全局唯一实例,可以为配置管理、日志记录、事件总线、数据库连接等场景提供一致的解决方案。在 JavaScript 中,我们可以通过对象字面量、闭包以及 ES6 类等方式实现单例模式,选择哪种方式取决于具体需求和项目复杂度。希望本文的多种示例能够帮助你在实际开发中更好地理解和应用单例模式

欢迎在评论区分享你的使用心得与疑问!


http://www.ppmy.cn/embedded/161944.html

相关文章

Linux之kernel(1)系统基础理论(2)

Linux之Kernel(1)系统基础理论(2) Author: Once Day Date: 2025年2月10日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章可参考专栏: Linux内核知识_Once-Day的…

【Linux】ip命令详解

Linux网络排查 目录 一、ip命令介绍 1.1 ip命令简介 1.2 ip命令的由来 二、ip命令使用帮助 2.1 ip命令的help帮助信息 2.2 ip命令对象介绍 2.3 ip命令选项介绍 三、查看网络信息 3.1 显示当前网络接口信息 3.2 显示网络设备运行状态 3.3 显示详细设备信息 3.4 查看…

【Gin】Web框架开发快速入门

本文目录 一、使用go.work创建工作区二、gin框架快速上手2.1 简单的请求实现2.2 URI2.3 分组路由2.4 Get请求参数获取2.5 Post请求参数2.6 响应 一、使用go.work创建工作区 依次输入下面命令,初始化工程。 mkdir ginlearn cd .\ginlearn\ mkdir helloworld go wor…

iOS主要知识点梳理回顾-5-运行时方法交换

方法交换可以放在 load 或 initialize 方法中,也可以自己根据时机来空,比如开启某个开关后才需要交换方法。如果是在load中调用,交换工作会在类加载时(程序启动)自动调用;如果是在initialize中调用&#xf…

python利用jenkins模块操作jenkins

安装python-jenkins 可以使用pip命令来安装python-jenkins模块: pip install python-jenkins操作jenkins 接下来就是连接和操作jenkins,写了个class,直接上代码 class Jenkins():def __init__(self, url, username, password):# jenkins服…

鸿蒙HarmonyOS NEXT开发:优化用户界面性能——组件复用(@Reusable装饰器)

文章目录 一、概述二、原理介绍三、使用规则四、复用类型详解1、标准型2、有限变化型2.1、类型1和类型2布局不同,业务逻辑不同2.2、类型1和类型2布局不同,但是很多业务逻辑公用 3、组合型4、全局型5、嵌套型 一、概述 组件复用是优化用户界面性能&#…

Eclipse 插件开发相关概念

整理了Eclipse插件开发的概念,用于熟悉入门 SWT(Standard Widget Toolkit)标准图形工具箱 Java开发的GUI程序技术,由Eclipse开发,相比AWT、Swing更美观;对于目标平台上已经有的控件,SWT会直接使…

【JavaWeb10】服务器渲染技术 --- JSP

文章目录 🌍一. JSP❄️1.JSP介绍❄️2.JSP 运行原理❄️3.page 指令(常用的)❄️ 4.JSP 三种常用脚本1.声明脚本2.表达式脚本3.代码脚本 ❄️5.JSP 内置对象❄️6.JSP 域对象 🌍二. EL❄️1.EL 表达式介绍❄️2.EL 运算操作❄️3.EL 的 11 个隐含对象 &…