Skip to content

享元模式

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 开销通常是值得的。