大厂面试必备知识点 :单例模式

75 阅读5分钟

单例模式,是设计模式中最简单且最常用的一种创建型模式。它的作用是确保一个类只有一个实例,并提供一个全局访问点。这种模式在资源管理、状态共享和性能优化中具有重要意义。

本文我将结合文档中的代码案例,从理论到实践带大家详细了解单例模式。


一、什么是单例模式?单例模式有什么用?

单例模式的定义:

单例模式(Singleton Pattern)是一种创建型设计模式,它能限制一个类仅能实例化一次,并通过提供一个全局访问点,让其他对象可以方便地获取该实例。

使用方式:

单例模式的使用方法有两种,分别是通过静态属性闭包变量保存唯一的实例,并使用静态方法函数封装来控制实例的创建逻辑。

下面,我将结合代码带大家对单例模式进行详细分析。


二、 通过 ES6 Class 实现单例(Storage 类)

先看代码:

class Storage {
    static instance; // 静态属性,用于保存唯一实例

    constructor() {
        console.log(this, "实例化一次"); // 构造函数只会执行一次
    }

    static getInstance() { // 静态方法,提供全局访问点
        if (!Storage.instance) {
            Storage.instance = new Storage(); // 第一次调用时创建实例
        }
        return Storage.instance; // 后续调用直接返回已有实例
    }

    getItem(key) { // 封装 localStorage.getItem 方法
        return localStorage.getItem(key);
    }

    setItem(key, value) { // 封装 localStorage.setItem 方法
        localStorage.setItem(key, value);
    }
}

// 使用方式
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
console.log(storage1 === storage2); // true,两者指向同一实例

上面这段代码,它定义了一个名为 Storage 的类,用于封装对 localStorage 的操作(getItemsetItem)。并且通过单例模式,确保它无论调用多少次 Storage.getInstance(),始终返回同一个实例。

代码分析:

  • 静态属性 static instance
    静态属性属于类本身,而不是类的实例,通过 Storage.instance ,我们可以在不创建实例的情况下访问该属性。

  • 静态方法 static getInstance()
    静态方法可以直接通过类名来调用(如 Storage.getInstance()),而无需实例化。它负责检查 instance 是否已存在,若不存在则创建新实例并保存到 instance 中。

  • 构造函数 constructor()
    构造函数仅在首次调用 getInstance() 时执行一次。后续调用会直接返回已存在的实例,避免重复初始化。

代码效果:
通过封装 localStorage 的操作,来实现单例的存储管理,这里无论调用多少次Storage.getInstance(),始终返回同一个实例。


三、 通过闭包实现单例(StorageBase 类)

代码解析

function StorageBase() {} // 基础类,定义公共方法

StorageBase.prototype.getItem = function(key) {
    return localStorage.getItem(key);
};

StorageBase.prototype.setItem = function(key, value) {
    localStorage.setItem(key, value);
};

// 闭包实现单例
const Storage = (function() {
    let instance = null; // 闭包变量,保存唯一实例
    return function() {
        if (!instance) {
            instance = new StorageBase(); // 第一次调用时创建实例
        }
        return instance; // 后续调用直接返回已有实例
    };
})();

// 使用方式
const storage1 = new Storage();
const storage2 = new Storage();
console.log(storage1 === storage2); // true,两者指向同一实例

上面的代码通过闭包,实现了一个单例模式的 Storage 对象,它通过立即执行函数(IIFE)定义一个闭包变量 instance,确保 Storage 类的实例在整个程序运行期间仅创建一次。

代码分析:

  • 闭包变量 instance
    这里通过立即执行函数(IIFE)创建的 instance 变量是一个自由变量,这个变量在外部无法直接访问或修改。
  • 返回的函数
    IIFE 返回的函数是一个工厂函数,在每次调用时,它都会检查 instance 是否存在,若不存在,则创建新实例并赋值给 instance
  • 原型链继承
    StorageBase 类的 getItemsetItem 方法定义在原型链上,使所有的实例共享这些方法。

代码效果: 即使通过 new Storage() 创建实例,会返回同一个对象,闭包隔离了 instance,避免外部直接修改实例。


闭包实现单例 vs ES6 Class 实现单例

闭包实现(StorageBase)ES6 Class 实现(Storage)
核心特性通过闭包变量保存唯一实例,外部无法直接访问,安全性高。通过类的静态属性保存实例,语法现代化,符合 ES6+ 标准。
使用方法工厂函数返回实例(如 new Storage()),需手动控制闭包作用域。静态方法 getInstance() 显式获取实例,避免直接使用 new Storage()
适用场景小型项目、工具类封装、对安全性要求高的场景(如敏感数据管理)。现代项目、团队协作、需要继承和扩展功能的场景(如模块化开发)。

优缺点分析

闭包实现(StorageBase)

  • 优点

    • 封装性更强:闭包变量完全私有,外部无法修改或覆盖实例,安全性更高。
    • 代码紧凑:通过闭包隔离逻辑,适合快速实现简单单例。
  • 缺点

    • 扩展性差:难以直接继承或组合其他类,需手动处理原型链。
    • 可读性低:依赖闭包概念,初学者可能较难理解。

ES6 Class 实现(Storage)

  • 优点

    • 语法清晰:符合现代 JavaScript 开发习惯,代码结构更易读。
    • 支持继承:天然支持 extends,便于功能扩展和模块化设计。
    • 维护性高:类方法集中管理,适合长期维护和团队协作。
  • 缺点

    • 安全性较低:静态属性可能被外部直接访问或篡改(如 Storage.instance = null)。
    • 需额外防护:需通过封装或私有属性(如 private static #instance)避免实例被破坏。

总结建议

  • 闭包实现:适合对安全性要求高、代码量小的场景,例如工具类或敏感配置管理。
  • ES6 Class 实现:更适合现代项目开发,尤其是需要团队协作、继承扩展或长期维护的场景。

四、总结

单例模式的核心价值

  • 保证唯一性:通过静态属性或闭包变量限制实例数量。
  • 全局访问:提供统一的访问入口,降低代码耦合度。
  • 性能优化:减少重复创建对象的开销,提升系统效率。

注意事项

  • 避免滥用:过度依赖单例可能导致代码难以测试和维护。
  • 线程安全:在多线程环境中需额外处理并发问题(JavaScript 无需考虑)。
  • 替代方案:在某些场景下,模块模式或依赖注入可能更合适。