前言
假如你在开发一个大型系统,里面有个“配置中心”对象,负责管理全局配置。
如果每个模块都能随意 new 一个配置中心,会发生什么?
- 资源浪费:每个模块都占用一份内存,明明只需要一份。
- 状态不一致:A模块改了配置,B模块还在用旧的,BUG满天飞。
- 维护困难:全局状态分散,排查问题像大海捞针。
就像公司有两个CEO,一个说向东,一个说向西,员工懵了,项目黄了。 想象一下,一家公司只能有一个CEO,所有重大决策都由他拍板。
而单例模式,就是让你的程序里某些“关键角色”像CEO一样,全局唯一。
单例 VS 非单例
非单例代码(每次new都不一样)
class Config {
constructor() {
this.theme = 'dark';
}
}
const a = new Config();
const b = new Config();
a.theme = 'light';
console.log(b.theme); // dark(状态不一致)
在这里我们定义了一个 Config 类,在构造函数中会初始化 theme 属性并赋值为 'dark'。当通过 new 关键字创建 Config 类的实例a和b时,二者属于相互独立的对象。当修改实例a的 theme 属性为 light 时,实例 b 的 theme 属性并不会发生改变,依旧保持初始的 dark 状态。
这清晰地表明,每次使用 new 创建的实例都是彼此独立的,它们之间的状态不会相互干扰。
单例代码(全局唯一)
class Config {
constructor() {
if (Config.instance) return Config.instance;
this.theme = 'dark';
Config.instance = this;
}
}
const a = new Config();
const b = new Config();
a.theme = 'light';
console.log(b.theme); // light(全局同步)
而这段代码同样定义了 Config 类,其构造函数会先检查 Config.instance 是否存在。若存在,则直接返回该实例;若不存在,就会初始化 theme 属性为 'dark',并将当前实例赋值给 Config.instance。如此一来,无论多少次使用 new 关键字去创建 Config 类的实例,实际上都只会得到同一个实例。当修改实例 a 的 theme 属性为 light 时,实例b的 theme 属性也会同步变为 light。
一句话总结:
单例模式确保全局仅有一个实例且状态完全同步,非单例模式每次 new 都生成独立实例且状态互不干扰。
实现方式
1. 我们可以通过一个本地存储(localStorage)管理类 Storage,并通过单例模式保证全局只有一个实例。
<!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>
<script>
class Storage {
constructor(namespace = 'storage') {
this.namespace = namespace;
}
static getInstance(){
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
getItem(key) {
return localStorage.getItem(this.namespace + key);
}
setItem(key, value) {
localStorage.setItem(this.namespace + key, value);
}
}
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
console.log(storage1 === storage2, '!!!'); // 输出: true '!!!'
storage1.setItem('name','haha');
console.log(storage1.getItem('name')); // 输出: "haha"
console.log(storage2.getItem('name')); // 输出: "haha"
</script>
</body>
</html>
核心思路是:通过 Storage.getInstance() 静态方法获取唯一实例,无论调用多少次,返回的都是同一个对象。这样可以确保所有数据操作都集中在同一个“存储管理员”手里,避免多实例带来的混乱和数据不一致。
在实际使用中,storage1 和 storage2 都是同一个实例。我们可以通过任意一个实例设置或获取本地存储的数据,数据始终保持同步。
2. 我们还可以用经典的ES5闭包+原型方式实现了单例模式:
function StorageBase() {
}
StorageBase.prototype.getItem = function(key) {
return localStorage.getItem(key);
}
StorageBase.prototype.setItem = function(key, value) {
return localStorage.setItem(key, value);
}
const Storage = (function() {
let instance = null;
return function() {
if(!instance) {
instance = new StorageBase();
}
return instance;
}
})();
const storage1 = Storage();
const storage2 = Storage();
console.log(storage1 === storage2)
首先,代码定义了一个基础构造函数 StorageBase,它本身没有内容,只是作为本地存储操作对象的模板。
接着,在 StorageBase 的原型上添加了两个方法:getItem 用于从本地存储读取数据,setItem 用于向本地存储写入数据。这样,所有通过 StorageBase 创建的对象都能直接使用这两个方法。
然后,代码通过一个立即执行函数表达式(IIFE)创建了 Storage 变量。这个 IIFE 内部声明了一个 instance 变量,用于保存唯一的实例。IIFE 返回的其实是一个函数,每次调用这个函数时,都会先判断 instance 是否已经有值。如果没有(说明是第一次调用),就用 new StorageBase() 创建一个新对象,并把它赋值给 instance。如果已经有了,直接返回这个已经创建好的对象。
最后,代码通过 const storage1 = Storage();和const storage2 = Storage(); 两次获取实例,并用 console.log(storage1 === storage2) 验证它们是否是同一个对象。结果为 true,说明无论调用多少次 Storage(),拿到的都是同一个实例,实现了单例模式。
主要区别对比
| 对比项 | ES6 类实现 | ES5 闭包实现 |
|---|---|---|
| 实现方式 | 使用 ES6 类语法 | 使用构造函数 + 原型 + 闭包 |
| 单例控制 | 通过静态方法 getInstance() 和静态属性 instance 实现 | 通过 IIFE 闭包和函数返回值控制 |
| 命名空间 | 支持命名空间,防止键名冲突 | 不支持命名空间 |
| 方法定义 | 方法直接定义在类中 | 方法定义在原型上 |
| 灵活性 | 较高,支持更多 ES6 特性 | 较低,依赖 ES5 特性 |
| 代码结构 | 更简洁,符合现代 JS 风格 | 较复杂,需要手动管理原型 |
单例模式的重要性
-
避免资源浪费
- 单例模式确保一个类只有一个实例,避免重复创建消耗系统资源(如数据库连接、网络请求)。
-
数据一致性
- 所有模块访问同一个实例,保证数据状态统一。例如,修改主题配置后,所有组件能立即感知变化。
-
简化代码结构
- 全局唯一实例减少了组件间传递依赖的复杂度,使代码更易维护。
-
线程安全(在多线程环境中)
- 在 Java、C# 等语言中,单例模式可设计为线程安全,避免多线程同时创建实例导致的问题。
常见应用场景
-
资源共享
- 示例:数据库连接池、文件系统操作、缓存系统
- 原因:避免重复创建多个相同资源,减少内存占用和性能开销。
-
全局状态管理
- 示例:应用配置、用户会话信息、主题设置
- 原因:确保所有组件访问的是同一份数据,状态变更统一生效。
-
日志记录器
- 示例:统一的日志输出工具
- 原因:避免多个日志实例导致输出混乱,确保日志顺序和格式一致。
-
UI 组件
- 示例:模态框、弹窗、通知组件
- 原因:防止同一时间出现多个相同组件实例,造成用户体验问题。
小结
单例模式不仅仅是一种设计模式,更是一种资源管理和状态控制的哲学。它通过限制实例数量,解决了资源浪费、状态不一致、线程安全等核心问题,是构建健壮、高效系统的基石。