JavaScript设计模式有哪些_如何应用单例模式【教程】

JavaScript单例模式本质是手动控制构造函数始终返回同一实例,核心在控制而非声明;需用闭包或静态私有字段缓存实例,并统一通过getInstance访问,避免new绕过控制。

JavaScript 中没有原生的“单例类”语法,所谓单例模式,本质是**手动控制一个构造函数只返回同一个实例对象**,关键在“控制”而非“声明”。

为什么 new 两次不等于两个实例?

单例的核心不是避免 new,而是确保多次调用构造逻辑后,始终返回同一引用。常见错误是只做了一次初始化但没缓存结果,或用了闭包却没暴露统一入口。

  • 直接在构造函数里判断 this instanceof Singleton 无效——每次 new 都会创建新对象
  • 用全局变量缓存(如 window.instance)污染作用域,且无法模块化
  • ES

    6 模块默认单例,但仅适用于无参、无状态的导出;有初始化逻辑时仍需手动控制

最稳妥的单例封装:闭包 + 静态属性

利用函数作用域保存私有实例,通过静态方法提供受控访问。比模块导出更灵活,支持延迟初始化和参数传入。

function Logger() {
  if (Logger.instance) {
    return Logger.instance;
  }
  this.logs = [];
  Logger.instance = this;
}
Logger.prototype.add = function(msg) {
  this.logs.push(msg);
};
Logger.getInstance = function() {
  return Logger.instance || new Logger();
};

调用时统一走 Logger.getInstance(),而不是 new Logger()。这样即使后续修改构造逻辑(比如加异步初始化),也只需改 getInstance 内部。

ES6 Class + 私有字段的现代写法

#instance 私有字段 + 静态 getter,语义更清晰,且避免意外覆盖静态属性:

class Database {
  static #instance = null;
  constructor(url) {
    if (!Database.#instance) {
      this.url = url;
      this.connection = null;
      Database.#instance = this;
    }
    return Database.#instance;
  }
  static getInstance(url) {
    if (!Database.#instance) {
      return new Database(url);
    }
    return Database.#instance;
  }
}

注意:new Database(url) 仍可能绕过控制——必须约定只用 getInstance。TypeScript 可加私有构造器进一步约束,但 JS 运行时无法强制。

真正容易被忽略的点

单例不是万能解耦方案。以下情况反而会让代码更难测、更难维护:

  • 实例持有 DOM 引用或定时器,未提供 destroy 方法,导致内存泄漏
  • 多个模块都调用 getInstance() 但传入不同参数(如不同 API 地址),后者会静默覆盖前者
  • 在 SSR 环境中,服务端多个请求共享同一实例,造成数据交叉污染

真需要“唯一性”的场景,优先考虑依赖注入或顶层 Provider(如 React Context、Vue provide/inject),而不是硬编码单例逻辑。