🚀 单例模式:前端开发中的 "唯一王者" 设计模式

93 阅读5分钟

一、开篇:为什么单例模式是前端 er 的好朋友?🧩

在前端开发中,设计模式就像一套 "代码套路",帮我们解决重复出现的问题。而单例模式,堪称其中的 "专一王者"—— 它能保证一个类一辈子只实例化一次,并且提供一个全局的 "入口" 让我们随时访问这个唯一实例。

比如:全局的本地存储管理、页面中唯一的登录弹窗、全局状态管理器…… 这些场景都需要 "独一份" 的实例,单例模式就是为这类需求量身定做的!💡

二、单例模式:核心特点大揭秘 🔑

单例模式的核心可以总结为两句话:

  1. 唯一实例:不管调用多少次实例化方法,始终只有一个实例对象(就像 "世界上只有一个月亮"🌙);
  2. 全局访问:有一个统一的方法可以获取这个实例(就像一把万能钥匙,随时能打开那扇门)。

为什么要这么设计?

  • 节省资源:避免重复创建实例(比如重复创建 DOM 元素、重复连接数据库);
  • 统一管理:全局只有一个实例,数据和方法更易同步(比如本地存储的数据,任何地方访问都是一致的)。

三、本地存储单例:两种实现方式手把手教学 📦

本地存储(localStorage)是前端常用的存储方案,用单例模式封装它,能避免重复实例化带来的资源浪费。下面两种实现方式,详细到每一行代码都给你讲清楚!

尊嘟假嘟.gif

方式 1:ES6 Class + 静态方法 🏗️

用 ES6 的 class 语法,通过静态属性和静态方法实现单例,逻辑清晰,一看就懂~

核心思路:

  • static instance保存唯一实例(相当于一个 "存档位");
  • static getInstance()方法控制实例创建:如果 "存档位" 为空,就新创建一个实例;否则直接返回已有的实例。

代码拆解:

class Storage {
  // 静态属性:保存唯一实例(初始为空)
  static instance; 

  // 构造函数:实例化时执行(仅第一次调用)
  constructor() {
    console.log("我是实例化时的日志~"); 
  }

  // 实例方法:存数据到localStorage
  setItem(key, value) {
    localStorage.setItem(key, value); 
  }

  // 实例方法:从localStorage取数据
  getItem(key) {
    return localStorage.getItem(key); 
  }

  // 静态方法:获取唯一实例的"入口"
  static getInstance() {
    // 关键判断:如果instance为空,就新创建一个;否则直接返回
    if (!this.instance) {
      this.instance = new Storage(); 
    }
    return this.instance; 
  }
}

验证单例效果:

// 两次获取实例
const storage1 = Storage.getInstance(); 
const storage2 = Storage.getInstance(); 

// 打印结果:true(证明是同一个实例!)
console.log(storage1 === storage2); 

// 用storage1存数据
storage1.setItem('name', '前端学习者'); 

// 用storage2取数据(能拿到!因为是同一个实例)
console.log(storage2.getItem('name')); // 输出:前端学习者

✨ 亮点:ES6 的 class 语法让代码更结构化,静态方法getInstance()直观地作为全局访问入口,新手也能快速理解~

方式 2:闭包 + 立即执行函数 🔄

闭包是 JavaScript 的 "黑魔法",用它实现单例,能更灵活地封装实例,隐藏内部细节。

核心思路:

  • 用 "立即执行函数" 创建一个独立作用域(相当于一个 "密室");
  • 在作用域内定义instance变量保存实例(只有闭包能访问);
  • 返回一个函数作为 "入口",每次调用时判断instance是否存在,决定是否创建新实例。

代码拆解:

// 1. 定义基础构造函数(负责实现存储功能)
function StorageBase() {}

// 2. 给原型添加存储方法(所有实例共享)
StorageBase.prototype.setItem = function(key, value) {
  localStorage.setItem(key, value); 
};
StorageBase.prototype.getItem = function(key) {
  return localStorage.getItem(key); 
};

// 3. 用闭包封装单例逻辑
const Storage = (function() {
  let instance = null; // 闭包内的变量,外部无法直接访问
  return function() { // 返回一个函数作为"入口"
    if (!instance) { // 第一次调用时创建实例
      instance = new StorageBase(); 
    }
    return instance; // 后续调用直接返回已有实例
  };
})(); // 立即执行函数,直接生成Storage"入口"

验证单例效果:

const storage1 = new Storage(); 
const storage2 = new Storage(); 

// 打印结果:true(还是同一个实例!)
console.log(storage1 === storage2); 

storage1.setItem('age', '18'); 
console.log(storage2.getItem('age')); // 输出:18

🤫 亮点:闭包把instance藏得严严实实,外部无法修改,更符合 "封装" 的设计原则,适合需要隐藏细节的场景~

四、登录弹窗单例:性能与体验双提升 🚪

登录弹窗是单例模式的经典应用!想象一下:如果用户多次点击 "登录" 按钮,每次都创建一个新弹窗,不仅 DOM 会乱套,还会浪费性能。用单例模式,让弹窗 "一生只出现一次"~

核心需求:

  • 弹窗全局唯一,无论点击多少次 "打开",都只创建一个 DOM 元素;
  • 初始不加载,等用户第一次点击时再创建(90% 用户可能不登录,提前加载纯纯浪费!⏳);
  • 支持 "打开 / 关闭" 操作,状态统一。

代码实现:

<!-- 按钮区域 -->
<button id="open">打开弹窗</button>
<button id="close">关闭弹窗</button>
<button id="open2">再打开一次</button>

<script>
// 用闭包实现弹窗单例
const Model = (function() {
  let model = null; // 保存唯一弹窗实例
  return function() {
    if (!model) { // 第一次调用时创建弹窗
      // 1. 创建DOM元素
      model = document.createElement('div'); 
      // 2. 设置内容和样式
      model.innerHTML = '我是唯一的登录弹窗~'; 
      model.id = 'model'; 
      model.style = `
        height: 200px; width: 200px; line-height: 200px;
        position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
        border: 1px solid #000; background: #fff; text-align: center;
        display: none; /* 初始隐藏 */
      `;
      // 3. 添加到页面
      document.body.appendChild(model); 
    }
    return model; // 后续调用直接返回已有弹窗
  };
})();

// 绑定按钮事件
document.getElementById('open').addEventListener('click', () => {
  const model = new Model(); 
  model.style.display = 'block'; // 显示弹窗
});

document.getElementById('close').addEventListener('click', () => {
  const model = new Model(); 
  model.style.display = 'none'; // 隐藏弹窗
});

document.getElementById('open2').addEventListener('click', () => {
  const model = new Model(); 
  model.style.display = 'block'; // 再次显示(还是同一个弹窗!)
});
</script>

!!??所以是真的哦.gif

为什么这么好?

  • 性能:只创建一次 DOM,避免重复渲染(🚀 速度 up up);
  • 体验:无论点击哪个 "打开" 按钮,都是同一个弹窗,状态不会乱;
  • 灵活性:延迟到第一次使用时创建,节省初始加载时间。

Suggestion.gif

五、总结:单例模式的 "三板斧" 🌟

  1. 唯一实例:通过静态属性或闭包保存实例,确保只创建一次;

  2. 全局访问:提供统一入口(如getInstance()或闭包返回的函数);

  3. 场景适配:适合全局资源(存储、弹窗、状态管理等),提升性能和可维护性。

下次遇到 "需要一个全局唯一实例" 的场景,就请出单例模式吧~ 它会像一个靠谱的管家,帮你把代码打理得井井有条!💼