从0开始学习JavaScript--JavaScript 单例模式

单例模式是一种常见的设计模式,它保证一个类仅有一个实例,并提供一个全局访问点。在 JavaScript 中,单例模式通常用于创建唯一的对象,以确保全局只有一个实例。本文将深入探讨单例模式的基本概念、实现方式,以及在实际应用中的各种场景。

单例模式的基本概念

单例模式的核心思想是确保一个类只有一个实例,并提供一个全局访问点。这样做的好处包括:

  1. 资源共享: 由于只有一个实例,可以避免多次创建相同对象,减少内存占用。
  2. 全局访问: 通过单一的入口访问对象,方便管理和控制。

在 JavaScript 中,实现单例模式有多种方式,我们将分别介绍其中的三种:懒汉式、饿汉式和模块模式。

懒汉式单例模式

懒汉式单例模式是指在需要时才创建实例,如果实例已经存在,则返回现有实例。这样可以延迟对象的创建,提高性能。

示例代码:

class LazySingleton {
  constructor() {
    if (!LazySingleton.instance) {
      this.data = Math.random(); // 示例中添加随机数,表示实例的一些数据
      LazySingleton.instance = this;
    }

    return LazySingleton.instance;
  }
}

const instance1 = new LazySingleton();
const instance2 = new LazySingleton();

console.log(instance1 === instance2); // 输出: true
console.log(instance1.data === instance2.data); // 输出: true

在这个示例中,LazySingleton 类只有在实例不存在时才创建新实例。之后,无论创建多少次实例,都返回第一次创建的实例。

饿汉式单例模式

饿汉式单例模式是指在应用启动时就立即创建实例,无论后续是否会使用到。

示例代码:

class EagerSingleton {
  constructor() {
    if (!EagerSingleton.instance) {
      this.data = Math.random();
      EagerSingleton.instance = this;
    }

    return EagerSingleton.instance;
  }
}

const instance1 = new EagerSingleton();
const instance2 = new EagerSingleton();

console.log(instance1 === instance2); // 输出: true
console.log(instance1.data === instance2.data); // 输出: true

在这个示例中,EagerSingleton 类在第一次创建实例时就立即创建了一个实例。之后,无论创建多少次实例,都返回第一次创建的实例。

模块模式的单例

模块模式是一种结合了闭包和立即调用函数表达式(IIFE)的方式,创建单例的模式。

示例代码:

const ModuleSingleton = (function () {
  let instance;

  function createInstance() {
    return {
      data: Math.random()
    };
  }

  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = ModuleSingleton.getInstance();
const instance2 = ModuleSingleton.getInstance();

console.log(instance1 === instance2); // 输出: true
console.log(instance1.data === instance2.data); // 输出: true

在这个示例中,ModuleSingleton 使用闭包和 IIFE 创建了一个包含 getInstance 方法的模块。getInstance 方法确保只有一个实例被创建,并提供全局访问点。

单例模式的实际应用场景

1. 管理全局状态

单例模式常用于管理全局状态,确保整个应用中只有一个状态管理实例,例如 Redux 中的 store。

const store = createStore(reducer);

2. 数据缓存

在需要缓存数据的场景,可以使用单例模式确保只有一个缓存实例。

class DataCache {
  constructor() {
    if (!DataCache.instance) {
      this.cache = {};
      DataCache.instance = this;
    }

    return DataCache.instance;
  }

  set(key, value) {
    this.cache[key] = value;
  }

  get(key) {
    return this.cache[key];
  }
}

const cache = new DataCache();
cache.set('user', { name: 'John' });
console.log(cache.get('user')); // 输出: { name: 'John' }

3. 配置管理

在配置管理中,使用单例模式可以确保只有一个配置管理实例,方便全局访问配置信息。

class ConfigurationManager {
  constructor() {
    if (!ConfigurationManager.instance) {
      this.config = { /* 配置信息 */ };
      ConfigurationManager.instance = this;
    }

    return ConfigurationManager.instance;
  }

  getConfig(key) {
    return this.config[key];
  }
}

const configManager = new ConfigurationManager();
console.log(configManager.getConfig('apiUrl')); // 输出: 配置信息中的 apiUrl

单例模式的进阶应用

1. 日志记录器

在应用中使用单例模式创建一个全局的日志记录器,确保只有一个实例记录所有日志信息。

class Logger {
  constructor() {
    if (!Logger.instance) {
      this.logs = [];
      Logger.instance = this;
    }

    return Logger.instance;
  }

  log(message) {
    this.logs.push(message);
    console.log(message);
  }

  printLogs() {
    console.log('All logs:');
    this.logs.forEach(log => console.log(log));
  }
}

const logger = new Logger();
logger.log('Log message 1');
logger.log('Log message 2');

const anotherLogger = new Logger();
console.log(logger === anotherLogger); // 输出: true
anotherLogger.printLogs(); // 输出: Log message 1 \n Log message 2

在这个例子中,Logger 类用于记录应用中的日志信息,通过单例模式确保只有一个全局的日志记录器。

2. 文件系统管理

在需要管理文件系统的应用中,使用单例模式可以确保只有一个实例负责文件系统的操作,避免文件冲突和资源竞争。

class FileSystemManager {
  constructor() {
    if (!FileSystemManager.instance) {
      this.files = [];
      FileSystemManager.instance = this;
    }

    return FileSystemManager.instance;
  }

  createFile(name) {
    this.files.push(name);
    console.log(`File '${name}' created.`);
  }

  listFiles() {
    console.log('Files in the system:');
    this.files.forEach(file => console.log(file));
  }
}

const fileSystem = new FileSystemManager();
fileSystem.createFile('document.txt');
fileSystem.createFile('image.jpg');

const anotherFileSystem = new FileSystemManager();
console.log(fileSystem === anotherFileSystem); // 输出: true
anotherFileSystem.listFiles(); // 输出: document.txt \n image.jpg

在这个例子中,FileSystemManager 类用于管理文件系统,确保只有一个实例负责文件的创建和列举。

3. 数据库连接

在需要管理数据库连接的应用中,使用单例模式可以确保只有一个实例负责数据库的连接,提高性能和资源利用率。

class DatabaseConnection {
  constructor() {
    if (!DatabaseConnection.instance) {
      this.isConnected = false;
      DatabaseConnection.instance = this;
    }

    return DatabaseConnection.instance;
  }

  connect() {
    if (!this.isConnected) {
      console.log('Database connected.');
      this.isConnected = true;
    } else {
      console.log('Already connected to the database.');
    }
  }

  disconnect() {
    if (this.isConnected) {
      console.log('Database disconnected.');
      this.isConnected = false;
    } else {
      console.log('Not connected to the database.');
    }
  }
}

const dbConnection = new DatabaseConnection();
dbConnection.connect();
dbConnection.disconnect();

const anotherDbConnection = new DatabaseConnection();
console.log(dbConnection === anotherDbConnection); // 输出: true
anotherDbConnection.connect(); // 输出: Already connected to the database.

在这个例子中,DatabaseConnection 类用于管理数据库连接,确保只有一个实例负责数据库的连接和断开。

单例模式的性能考虑

尽管单例模式确保只有一个实例存在,但在大型应用中,可能会导致全局状态的集中管理,增加了代码的耦合性。此外,在多线程环境中,需要考虑线程安全性,避免因为竞态条件而导致的问题。

在性能要求较高的场景,可以根据具体需求选择使用懒汉式或饿汉式单例模式。懒汉式能够延迟实例的创建,降低了启动时的负载,但在首次访问时可能会有性能开销。饿汉式则在应用启动时立即创建实例,保证了全局的唯一性,但可能增加了启动时间。

总结

JavaScript 单例模式是一种有力的设计模式,旨在确保一个类仅有一个实例,并提供一个全局访问点。通过懒汉式、饿汉式和模块模式等多种实现方式,开发者可以根据具体场景选择适合的单例模式,使得代码更为灵活和可维护。

在懒汉式中,实例在首次访问时被创建,延迟加载有助于降低启动时的负载。而饿汉式在应用启动时即创建实例,保证了全局唯一性,但可能增加了启动时间。模块模式结合了闭包和IIFE,为单例提供了一种更为模块化和安全的实现方式。

单例模式在实际应用中有着广泛的应用,包括管理全局状态、数据缓存、配置管理等方面。通过确保唯一实例的存在,单例模式提高了代码的可维护性和可读性,使得应用在全局范围内具备更好的控制和管理能力。

然而,开发者在使用单例模式时需要注意全局状态的管理可能带来的代码耦合问题。在性能要求较高的场景,可以选择懒汉式或饿汉式单例,根据具体需求权衡延迟加载和启动时间的取舍。综合而言,JavaScript 单例模式为项目提供了更好的架构和代码组织方式,让代码充满设计模式的智慧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/202197.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

const 和 constexpr 深入学习

在 C 中,const 和 constexpr 都可以用来修饰对象和函数。修饰对象时,const 表示它是常量,而 constexpr 表示它是一个常量表达式。常量表达式必须在编译时期被计算1。修饰函数时,const 只能用于非静态成员的函数,而 con…

Secure Software Lifecycle Management (SSLM)安全软件生命周期管理

文章目录 前言一、现代理念二、安全的软件生命周期管理总结 前言 The concept of integrating security into the software development process is not new. While I cannot definitively assert that Microsoft was the pioneer of this concept, the Secure Development Li…

清理docker Build Cache缓存文件

使用docker构建镜像,发现docker的overlay2文件会越来越大。 使用命令查看docker系统占用资源: docker system df 可以看到已经占用了26.7GB,清理这个缓存 docker builder prune 再次查看,已经没有缓存了,清理成功。 …

【UE】中文字体 发光描边材质

效果 步骤 1. 先将我们电脑中存放在“C:\Windows\Fonts”路径下的字体导入UE 点击“全部选是” 导入成功后如下 2. 打开导入的“SIMSUN_Font”,将字体缓存类型设置为“离线” 点击“是” 这里我选择:宋体-常规-20 展开细节面板中的导入选项 勾选“使用距…

Redis缓存淘汰策略

Redis缓存淘汰策略 1、各种面试题 生产上你们的redis内存设置多少?如何配置、修改redis的内存大小如果内存满了你怎么办?redis清理内存的方式?定期删除和惰性删除了解过吗?redis缓存淘汰策略有哪些?分别是什么?你用哪个?redis的LRU了解过吗?请手…

FastApi接收不到Apifox发送的from-data字符串_解决方法

接收不到Apifox发送的from-data字符串_解决方法 问题描述解决方法弯路总结弯路描述纵观全局小结 问题描述 这里写了一个接口,功能是上传文件,接口参数是file文件和一个id字符串 gpt_router.post("/uploadfiles") async def create_upload_fi…

基于ora2pg迁移Oracle19C到postgreSQL14

📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】!😜&am…

linux socket套接字

文章目录 socket流socket(TCP)数据报socket(UDP) 讨论 socket 所谓套接字,就是对网络中不同主机上的应用程序之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,套接字提供了应用层进程利…

AI - FlowField(流场寻路)

FlowField流场寻路,利用网格存储每个点对目标点的推力,网格上的单位根据对于推力进行移动。用于大量单位进行寻路对于同一目的地的寻路,常用于rts游戏等。 对应一张网格地图(图中黑块是不可行走区域) 生成热度图 计算所有网格对于目标点(…

【Flutter】graphic图表实现tooltip一段时间后自动隐藏

概述 graphic图表中提供了自定义tooltip的事件,可通过selections中on和clear配置手势选项和可识别设备,默认情况下tooltip需要双击隐藏,但这并不符合我们的需求。通过调研发现,若想实现tooltip隔几秒后隐藏,可通过Str…

西工大网络空间安全学院计算机系统基础实验一(9, 10, 11, 12, 13)

还是那句话,专心做好你自己的,老老实实把基础打好,不要被其他人带跑节奏,不要跟他打,跟着这系列博客,稳扎稳打一步一步来。即使你VMware workstation没下载好,即使你Ubuntu虚拟机没配好&#xf…

【数据挖掘】国科大刘莹老师数据挖掘课程作业 —— 第三次作业

Written Part 1. 基于表 1 1 1 回答下列问题(min_sup40%, min_conf75%): Transaction IDItems Bought0001{a, d, e}0024{a, b, c, e}0012{a, b, d, e}0031{a, c, d, e}0015{b, c, e}0022{b, d, e}0029{c, d}0040{a, b, c}0033{a, d, e}0038…

【计算机网络笔记】交换机

系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…

Vue3依赖注入

适用场景 尤其针对一个变量需要从顶层组件开始透传,途径很多个子组件最后在第n代子组件使用的时候。对于这些途经的子组件而言,它们不但不使用而且完全不关心该变量具体是什么,只是作为一个传递工具罢了。这种情况下,使用依赖注入…

asla四大开源组件应用示例(alsa-lib、alsa-utils、alsa-tools、alsa-plugins)

文章目录 alsa设备文件/dev/snd//sys/class/sound/proc/asoundalsa-lib示例1alsa-utilsalsa-toolsalsa-plugins参考alsa设备文件 /dev/snd/ alsa设备文件目录位于,/dev/snd,如下所示 root@xboard:~#ls /dev/snd -l total 0 drwxr-xr-x 2 root root 60 Nov 6 2023 …

vuepress-----7、发布在GitHub

# 7、发布在GitHub 在你的项目中,创建一个如下的 deploy.sh 文件(请自行判断去掉高亮行的注释): #!/usr/bin/env sh# 确保脚本抛出遇到的错误 set -e# 生成静态文件 npm run docs:build# 进入生成的文件夹 cd docs/.vuepress/dist# 如果是发…

PTA_2023年软件设计综合实践_10(回溯法与分治限界法)

7-1 桥本分数 将1-9九个数不重复地赋给不同的9个元素 ,实现形如a/bcd/eff/hi 的形式。例:1/265/784/39 1/325/967/84 (注意:1/265/784/39 和5/781/264/39 只能算一种解),共有多少种不同的解。 语言选C #…

36 - 电商系统表设计优化案例分析

如果在业务架构设计初期,表结构没有设计好,那么后期随着业务以及数据量的增多,系统就很容易出现瓶颈。如果表结构扩展性差,业务耦合度将会越来越高,系统的复杂度也将随之增加。这一讲我将以电商系统中的表结构设计为例…

服务器数据恢复—V7000存储raid5崩溃导致上层卷无法使用的数据恢复案例

服务器数据恢复环境: 某品牌V7000存储中有一组由几十块硬盘组建的raid5阵列。上层操作系统为windows server,NTFS分区。 服务器故障: 有一块硬盘出现故障离线,热备盘自动上线替换离线硬盘。在热备盘上线同步数据的过程&#xff0c…

【springboot】idea项目启动端口被占用

问题 idea本地启动springboot项目端口老是被占用 解决 关闭被占用的端口进程 步骤: 1. winR打开程序框 2. 查出被占用端口的进程id netstat -ano | finderstr 端口号 例如 netstat -ano | finderstr 81013.杀死进程 taskkill /pid 进程id -t -f 例如 taskkill /pid 2…