单例模式在javaScript中靠开发者主动控制实现,常用闭包+IifE封装私有实例或es6 class+静态属性实现,需禁用直接new、注意跨环境污染及避免滥用为全局状态。

单例模式在 javascript 中不是靠语言机制强制实现的,而是靠开发者主动控制实例创建逻辑——只要确保全局只存在一个实例,并能重复获取它,就算达成目标。
用闭包 + 模块模式封装私有实例
这是最常用、也最符合 js 特性的写法。利用立即执行函数(IIFE)和闭包变量,把实例“藏”在作用域里,外部无法篡改。
const Singleton = (function() { let instance = NULL; function createInstance() { return { data: Math.random(), log() { console.log('Singleton:', this.data); } }; } return { getInstance() { if (!instance) { instance = createInstance(); } return instance; } }; })(); // 使用 const a = Singleton.getInstance(); const b = Singleton.getInstance(); console.log(a === b); // true
用 ES6 class + 静态属性实现
更接近传统 OOP 写法,语义清晰,但要注意 instance 是静态属性,仍需手动检查是否已存在。
- 不能直接 new 类来绕过单例逻辑——必须通过
getInstance() - 如果类有继承需求,需额外处理子类的
instance独立性 - 若构造函数抛错,
instance可能为null或未定义,建议加 try/catch
class Logger { constructor() { if (Logger.instance) { return Logger.instance; } this.id = Date.now(); Logger.instance = this; } static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } log(msg) { console.log(`[${this.id}] ${msg}`); } } // 使用 const l1 = Logger.getInstance(); const l2 = Logger.getInstance(); console.log(l1 === l2); // true
注意:new 操作符和构造函数陷阱
JS 的 new 本身不阻止多次实例化。如果你允许用户直接 new Singleton(),那单例就失效了。
立即学习“Java免费学习笔记(深入)”;
- 不要只靠
if (instance) return instance放在构造函数里——new会忽略 return 的非对象值 - 若返回原始值(如
return 42),new仍返回新对象;只有返回对象才可能拦截 - 真正安全的做法是:禁止直接 new,只暴露工厂方法(如
getInstance),或用 symbol 私有标识做运行时校验
真实项目中单例常被误用的地方
单例不是“全局状态”的代名词。比如把用户配置、API Token、主题设置等全塞进一个单例里,会导致耦合、难以测试、无法多实例隔离(比如同时管理多个账号)。
- 优先考虑依赖注入(DI)或模块顶层变量,比手写单例更易维护
- 浏览器扩展、微前端、SSR 环境下,单例容易跨上下文污染(比如服务端渲染时共享了客户端实例)
- 如果需要“每个 tab 独立单例”,得结合
window.name或localStorage做命名空间隔离
单例的关键不在“怎么写”,而在“为什么必须唯一”。没想清楚生命周期、共享边界和销毁时机,代码越“规范”越难调试。