单例模式真正解决的不是“只能 new 一次”,而是统一资源入口

单例模式真正解决的不是"只能 new 一次",而是统一资源入口

看到下面这段代码时,很多人会先记住 a === b 是 true:

js 复制代码
const a = Popup.getInstance();
const b = Popup.getInstance();
console.log(a === b); // true

但面试或实际项目中,更重要的问题是:实例由谁创建?谁保存它?其他代码能不能绕过这个入口?本文用一个打开网页的 Popup 示例,把这条逻辑拆开。源码静态阅读,运行未验证。

先看最小模型

text 复制代码
Popup 类
  ↓
静态属性 ins 保存实例
  ↓
getInstance 统一返回实例
  ↓
按钮事件调用实例方法

单例模式的核心不是魔法,而是资源生命周期的集中管理。

getInstance 做了两件事

js 复制代码
class Popup {
  static ins;

  static getInstance() {
    if (!Popup.ins) {
      Popup.ins = new Popup();
    }
    return Popup.ins;
  }
}

第一件事:检查实例是否存在。

第二件事:不存在时创建,存在时复用。

所以执行顺序是:

text 复制代码
第一次调用:undefined → new Popup() → 保存 → 返回
第二次调用:已有实例 → 不创建 → 直接返回

static 让 getInstance() 在没有实例时也可以直接通过类调用:

js 复制代码
Popup.getInstance();

如果它不是静态方法,就必须先有一个对象,这与"由它负责创建第一个对象"的目标矛盾。

a === b 证明了什么

js 复制代码
const a = Popup.getInstance();
const b = Popup.getInstance();

两次调用返回的都是 Popup.ins。对于对象,=== 比较的是引用,因此 true 表示两个变量指向同一个实例,而不是仅仅"两个对象内容相同"。

这也是单例示例中最直接的验证方式。

和按钮事件连起来

js 复制代码
const openBtn = document.getElementById("openBtn");
openBtn.addEventListener("click", () => {
  a.open("https://www.baidu.com");
});

页面先找到按钮,再监听点击事件。点击发生后,调用之前获取的单例对象。

这体现了一个可迁移的工程思路:

text 复制代码
页面事件只负责触发
业务对象负责执行
实例获取由统一入口负责

这个例子目前并不完整

Popup 类里没有 open(),但点击事件调用了它:

js 复制代码
a.open("https://www.baidu.com");

因此运行时可能出现:

text 复制代码
TypeError: a.open is not a function

源码中被注释的旧写法是:

js 复制代码
window.open("https://www.baidu.com ", "_blank");

它暗示打开网页的行为应该封装到 Popup 中,但当前材料没有给出完整实现。这个问题不能被"单例模式本身正确"掩盖:设计模式解决结构问题,不能自动补齐缺失的方法。

初学者自检表

  • static 属性属于类还是实例?
  • 第一次调用是否真的执行了 new Popup()?
  • 第二次调用是否复用了缓存?
  • a === b 比较的是值还是引用?
  • 点击事件调用的方法是否在类中存在?
  • 是否有人绕过 getInstance() 直接 new Popup()?

单例模式的边界

当前代码通过静态属性实现了"单例式缓存",但没有禁止外部直接调用:

js 复制代码
new Popup();

因此它依赖调用约定,而不是严格限制实例数量。对于很多前端小项目,这种实现足够直观;如果要做更严格的限制,还需要进一步封装构造过程。

最后一个判断

单例模式值得学习的地方,不是背下 getInstance 模板,而是理解资源管理:

当一个对象应该被多个地方共享时,把创建、缓存和访问规则集中起来,能减少重复初始化和状态分散。

这个 Popup 示例还缺少 open() 方法。补齐后再验证弹窗行为,才能把"设计意图"和"实际可运行代码"区分开。运行结果当前未验证。

标签: JavaScript, 单例模式, 设计模式, 前端开发

相关推荐
夏幻灵6 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
三天不学习7 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194057 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
小宋102113 小时前
A2UI从零实战:Agent安全生成交互界面与事件回传
javascript·人工智能·安全·交互
大包子13 小时前
structuredClone vs lodash.cloneDeep:核心差异与选型指南
javascript·vue.js
lerhxx13 小时前
AI 应用中的上下文管理 —— 从"上下文窗口"到"分层压缩"的工程实践
前端·javascript
Mahut14 小时前
我在 Mac 上做了个本地剪辑器
javascript·架构·产品
汉堡大王952714 小时前
GPT-6 上线 48 小时,我扒开了 Intelligent UI 的运行机制:DIL、沙箱 Worker 和一个 React 式协调器
前端·javascript·后端
hai_android14 小时前
Chat 聊天模块功能总结
前端·javascript·vue.js
创世虚拟世界15 小时前
我做了一个 3D 虚拟世界基底,基于这个再做 3D 虚拟世界或者元宇宙,会事半功倍
javascript·人工智能·3d·gitee·虚拟现实