享元模式
1. 核心概念与价值
享元模式的定义是:运用共享技术有效地支持大量细粒度的对象。
它的核心思想是:区分内部状态和外部状态,将可以共享的内部状态提取出来,实现对象的复用。
| 维度 | 描述 |
|---|---|
| 核心意图 | 通过共享技术减少内存中对象的数量。 |
| 内部状态 (Internal) | 存储在享元对象内部、可以被共享的信息。它独立于场景,不会随环境改变(如:衣服的性别)。 |
| 外部状态 (External) | 取决于具体场景、无法共享的信息。它会随环境改变,通常由外部注入(如:穿衣服的人)。 |
| 主要优点 | 极大减少内存占用,提升程序性能。 |
| 主要缺点 | 增加了系统的复杂度,需要花费时间去拆分内部和外部状态。 |
2. 模式演进:从“内存溢出”到“按需共享”
让我们以一个 “内衣工厂模特展示” 的案例来看享元模式。
2.1 传统写法(反面教材:创建 10000 个对象)
如果要给 5000 男款和 5000 女款衣服拍照,按普通思路要请 10000 个模特,内存直接爆掉。
js
class Model {
constructor(sex, underwear) {
this.sex = sex;
this.underwear = underwear;
}
takePhoto() {
console.log(`性: ${this.sex}, 穿: ${this.underwear} 拍照`);
}
}
// 循环 10000 次,内存占用极大
for (let i = 1; i <= 5000; i++) {
const maleModel = new Model('male', `男款第${i}套`);
maleModel.takePhoto();
}2.2 享元模式写法(解耦版:仅需 2 个对象)
我们只需要一个男模特和一个女模特,让他们轮流穿不同的衣服拍照即可。
js
// 1. 享元对象:只保留内部状态(性别)
class Model {
constructor(sex) {
this.sex = sex;
}
takePhoto(underwear) { // 外部状态通过参数传入
console.log(`性别: ${this.sex}, 穿: ${underwear} 拍照`);
}
}
// 2. 享元工厂:负责创建和管理对象,确保唯一性
const modelFactory = (function() {
const cache = {}; // 存储已创建的单例
return {
create: function(sex) {
if (cache[sex]) return cache[sex];
return cache[sex] = new Model(sex);
}
};
})();
// 3. 外部状态管理器:控制 10000 次逻辑,但只用 2 个实例
for (let i = 1; i <= 5000; i++) {
const maleModel = modelFactory.create('male');
maleModel.takePhoto(`男款第${i}套`);
}
for (let i = 1; i <= 5000; i++) {
const femaleModel = modelFactory.create('female');
femaleModel.takePhoto(`女款第${i}套`);
}3. 实战场景
| 场景 | 享元模式的应用 |
|---|---|
| DOM 性能优化 | 如果列表有 10 万行,不直接生成 10 万个节点,而是只生成可见区域的几十个节点,滚动时复用节点并更新数据(虚拟滚动的基础)。 |
| 游戏开发 | 森林中有 10 万棵树。每棵树的形状(内部状态)可能只有 5 种,但位置和颜色(外部状态)不同。 |
| 对象池 (Object Pool) | 在线程、数据库连接、甚至游戏中子弹的创建中,预先创建一组对象重复使用,而不是频繁创建和销毁。 |
4. 常见问题 (FAQ)
4.1 享元模式和单例模式有什么区别?
- 答:
- 单例模式:整个应用只有一个唯一的实例(如全局 Store)。
- 享元模式:可以有一组不同的共享对象(如 Model 工厂里有一个‘男’和一个‘女’)。享元模式的目标是减少数量。
4.2 什么时候应该考虑使用享元模式?
- 答:只有当满足以下条件时才推荐:
- 一个程序使用了大量的对象(上千甚至更多)。
- 对象的大部分状态都可以变为外部状态。
- 如果不使用享元模式,内存开销将难以承受。
- 注意:如果对象很少,强行拆分反而会让代码变复杂且没意义。
4.3 如何存储外部状态?
- 答:由于外部状态不再存储在对象内部,通常需要一个外部的管理器(Manager)或者一个哈希表来存储每个对象对应的外部信息。在 JavaScript 中,经常利用闭包或者 Map/Set 来实现。
4.4 享元模式会增加 CPU 负担吗?
- 答:是的。 享元模式是一种典型的**“空间换时间”**(或“时间换空间”)策略。你需要花费额外的计算时间来拆分、提取和注入外部状态。但相比于内存溢出(OOM)导致的页面卡死,这点 CPU 开销通常是值得的。