🎯 JavaScript 单例模式(Singleton Pattern)—— 从理论到实战

📖 什么是单例模式?

单例模式是 OOP(面向对象编程) 中最经典的设计模式之一,属于 GoF 23 种设计模式中的一种。

💡 核心思想: 一个类在系统中只能被实例化一次,无论调用多少次,返回的都是同一个实例。

单例模式常用于解决以下场景:

🔥 场景 说明
全局状态管理 整个应用只需要一个管理器(如 Vuex Store、Redux Store)
弹窗/窗口控制 页面中只允许打开一个弹窗窗口
数据库连接池 避免重复创建昂贵的连接资源
缓存管理 全局共享同一份缓存数据

🧠 为什么需要单例模式?

假设我们不用单例模式,每次需要弹窗时都 new 一个新对象:

js 复制代码
const a = new Popup();
const b = new Popup();
console.log(a === b); // ❌ false ------ 这是两个完全不同的实例!

这意味着每次点击按钮都会创建一个新的弹窗对象,如果对象内部持有大量资源(DOM引用、网络连接等),就会造成:

  • 🔴 内存浪费 ------ 重复创建不需要的对象
  • 🔴 状态不一致 ------ 多个实例各自维护状态,互相不同步
  • 🔴 行为不可控 ------ 比如重复打开多个窗口

单例模式正是为了确保全局只有一个实例而存在的。


✅ 单例模式的实现

下面是一个最简洁、最经典的 JavaScript 单例模式实现:

js 复制代码
class Popup {
  static ins; // ⬅️ 静态属性,存储唯一的实例

  static getInstance() {
    if (!Popup.ins) {
      Popup.ins = new Popup(); // 🔄 第一次调用时创建实例
    }
    return Popup.ins; // 🔁 后续调用直接返回已有实例
  }

  open(url) {
    window.open(url, "_blank");
  }
}

🔑 三个关键要素

要素 代码 作用
① 静态属性 static ins 作为类级别的"存储容器",不绑定到任何实例
② 延迟初始化 if (!Popup.ins) 只在首次调用时才创建实例(惰性加载)
③ 静态方法 static getInstance() 提供统一的获取入口,替代 new 关键字

⚠️ 注意: 不要在声明静态属性时直接赋值 static ins = new Popup(),这会导致类加载时就立即实例化,失去了"延迟初始化"的优势。


🧪 验证:真的是同一个实例吗?

js 复制代码
const a = Popup.getInstance(); // 第一次获取
const b = Popup.getInstance(); // 第二次获取

console.log(a === b); // ✅ true ------ a 和 b 指向同一个对象

用一张图来理解:

scss 复制代码
调用 getInstance()
       │
       ▼
  ┌──────────┐
  │ ins 存在? │
  └─────┬────┘
        │
   ┌────┴────┐
   No        Yes
   │          │
   ▼          ▼
 new Popup()  直接返回 ins
   │
   ▼
 赋值给 ins,然后返回

无论调用多少次 getInstance(),始终返回的是同一个 Popup 实例。 🎉


🖥️ 完整实战:单例模式打开窗口

将单例模式应用到实际的页面交互中:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>单例模式打开窗口</title>
</head>
<body>
  <button id="openBtn">打开窗口</button>

  <script>
    // ========== ① 定义单例类 ==========
    class Popup {
      static ins;
      static getInstance() {
        if (!Popup.ins) {
          Popup.ins = new Popup();
        }
        return Popup.ins;
      }
      open(url) {
        window.open(url, "_blank");
      }
    }

    // ========== ② 通过 getInstance 获取实例(代替 new) ==========
    const popup = Popup.getInstance();

    // ========== ③ 绑定事件 ==========
    const openBtn = document.getElementById("openBtn");
    openBtn.addEventListener("click", () => {
      popup.open("https://www.baidu.com");
    });
  </script>
</body>
</html>

🔍 为什么不用 new Popup() ? 使用 getInstance() 能够保证全局只有一个实例 。如果用 new,每次都会创建新对象,就失去了单例的意义。


🌟 单例模式的优缺点

✅ 优点

  • 🏆 全局唯一 ------ 保证系统中该类只有一个实例
  • 💾 节省资源 ------ 避免重复创建和销毁对象
  • 🔄 状态共享 ------ 所有使用者访问同一个实例,状态天然同步

❌ 缺点

  • 🚫 违反单一职责 ------ 类既要负责业务逻辑,又要管理自己的实例化
  • 🧪 测试困难 ------ 全局状态可能导致测试之间互相影响
  • 🔒 不够灵活 ------ 一旦实现为单例,后续难以扩展为多实例

📝 总结

维度 内容
是什么 保证一个类只有一个实例,并提供全局访问点
怎么实现 static 属性存储实例 + getInstance() 延迟初始化
核心判断 if (!Instance) → 不存在则创建,存在则直接返回
适用场景 弹窗管理、全局状态、连接池、缓存等
一句话 🎯 用 getInstance() 代替 new,全局只有一个对象

💬 单例模式虽然简单,却是学习设计模式的绝佳入口。掌握了它,你就迈出了从"写代码"到"设计代码"的第一步。

相关推荐
酷炫码神18 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
酷炫码神20 小时前
Day 8|Vue3 响应式:告别手动操作 DOM,让数据驱动界面
javascript·vue.js·ecmascript
码林鼠21 小时前
flutter状态管理入门教程-Riverpod
开发语言·javascript·flutter
互影派21 小时前
分享:Cocos 互动影游一套代码双端分发(Web H5 + Windows/Steam)实践
javascript
Amos_Web1 天前
Markdonw语法
开发语言·前端·javascript
全栈项目管理程序猿1 天前
ArcGIS JS 基础教程(34):体元系列 - 等值面 VoxelIsosurface
前端·javascript
SensorFlow1 天前
离页埋点:sendBeacon 返回 true,为什么还不能算接收成功
javascript
兔子零10241 天前
架构师最危险的能力:用正确的技术,做出错误的架构决策
前端·javascript·后端
Bug收容所1 天前
Vue+js Day2
前端·javascript·vue.js
掘金酱1 天前
带着作品来,坐下来聊|掘金 X-lab · Coffee Talk 邀请你来见一面
前端·javascript·后端