单例模式核心解析

  1. 单例模式是一种创建型设计模式,核心目标是保证一个类在程序生命周期内只有一个实例,并提供一个全局访问点。简单来说,无论多少次尝试创建这个类的实例,最终得到的都是同一个对象,避免重复创建导致的资源浪费;
  2. 单例模式核心特征
    1. 私有静态实例:类内部维护一个唯一的实例对象,外部无法直接访问;
    2. 私有构造函数:防止外部通过 new 关键字直接创建新实例;
    3. 公有静态方法:提供全局访问点,用于获取 / 创建唯一实例;
  3. 类图:
  4. 实现代码:
    // 饿汉式单例 - 立即创建实例
    class SingletonEager {
        // 静态实例 - 类加载时就创建
        static instance = new SingletonEager();
        
        // 私有构造函数(JS中无法真正私有,但用约定表示)
        constructor() {
            if (SingletonEager.instance) {
                throw new Error('单例类不能直接实例化,请使用 getInstance() 方法');
            }
            
            // 初始化属性
            this.createdAt = new Date();
            
            console.log('饿汉式单例实例被创建');
        }
        
        // 静态方法获取实例
        static getInstance() {
            return SingletonEager.instance;
        }
    }
    
    // 测试饿汉式单例
    console.log('=== 饿汉式单例测试 ===');
    try {
        const instance1 = SingletonEager.getInstance();
        const instance2 = SingletonEager.getInstance();
        
        console.log('实例是否相同:', instance1 === instance2); // true
        
        // 尝试直接实例化
        // const instance3 = new SingletonEager(); // 抛出错误
    } catch (error) {
        console.error('错误:', error.message);
    }
    
    // 懒汉式单例 - 需要时才创建
    class SingletonLazy {
        // 静态实例引用
        static #instance = null; // 使用私有字段
        
        // 私有构造函数
        constructor() {
            if (SingletonLazy.#instance) {
                throw new Error('单例类不能直接实例化,请使用 getInstance() 方法');
            }
            
            // 初始化属性
            this.createdAt = new Date();
            console.log('懒汉式单例实例被创建');
        }
        
        // 静态方法获取实例(延迟加载)
        static getInstance() {
            // 双重检查锁定(JS中不需要真正的锁,但保留逻辑)
            if (!SingletonLazy.#instance) {
                console.log('首次调用,创建实例');
                SingletonLazy.#instance = new SingletonLazy();
            }
            return SingletonLazy.#instance;
        }
    }
    
    // 测试懒汉式单例
    console.log('\n=== 懒汉式单例测试 ===');
    console.log('此时实例还未创建');
    
    const lazy1 = SingletonLazy.getInstance();
    const lazy2 = SingletonLazy.getInstance();
    
    console.log('实例是否相同:', lazy1 === lazy2); // true
    
    const SingletonWithClosure = (function () {
        let instance = null; // 私有实例(对应类图的 #instance)
    
        // 私有构造逻辑(对应类图的 constructor)
        function createInstance() {
            return {
                name: "闭包单例示例",
                businessMethod: () => console.log("执行闭包单例业务逻辑"),
            };
        }
    
        // 公有访问方法(对应类图的 getInstance)
        return {
            getInstance: () => {
                if (!instance) {
                    instance = createInstance();
                }
                return instance;
            },
        };
    })();
    
    // 测试
    const c1 = SingletonWithClosure.getInstance();
    const c2 = SingletonWithClosure.getInstance();
    console.log("c1 === c2:", c1 === c2); // 输出:true
    

常用使用场景

场景 1:全局状态管理(如 Vuex/Pinia 核心、自定义全局存储)

  1. 原理:应用的全局状态需要唯一数据源,避免多实例导致状态不一致;

  2. 示例:PiniadefineStore 创建的 store 本质是单例,整个应用中同一个 store 只会实例化一次;

场景 2:弹窗 / 模态框(Modal/Dialog)

  1. 原理:页面中同类型弹窗 (如登录弹窗) 无需重复创建 DOM 节点,复用同一个实例可减少 DOM 操作,提升性能;

  2. 示例:

    // 弹窗单例实现
    const ModalSingleton = (() => {
        let modalInstance = null;
    
        function createModal() {
            const div = document.createElement("div");
            div.className = "modal";
            div.style.display = "none";
            document.body.appendChild(div);
            return {
                show: () => (div.style.display = "block"),
                hide: () => (div.style.display = "none"),
                setContent: (content) => (div.innerHTML = content),
            };
        }
    
        return {
            getInstance: () => {
                if (!modalInstance) {
                    modalInstance = createModal();
                }
                return modalInstance;
            },
        };
    })();
    
    // 多次调用仅创建一个弹窗 DOM
    ModalSingleton.getInstance().show();
    ModalSingleton.getInstance().setContent("登录成功!");
    

场景 3:全局缓存管理

  1. 原理:全局缓存 (如接口数据缓存) 需要唯一的缓存容器,避免多实例导致缓存数据不一致;

  2. 示例:

    const CacheSingleton = {
        // 单例实例(全局唯一)
        instance: null,
        getInstance() {
            if (!this.instance) {
                this.instance = {
                    cache: new Map(),
                    set: (key, value) => this.instance.cache.set(key, value),
                    get: (key) => this.instance.cache.get(key),
                };
            }
            return this.instance;
        },
    };
    
    // 接口请求缓存示例
    async function fetchData(url) {
        const cache = CacheSingleton.getInstance();
        if (cache.get(url)) {
            return cache.get(url); // 复用缓存数据
        }
        const res = await fetch(url);
        const data = await res.json();
        cache.set(url, data); // 存入缓存
        return data;
    }
    

场景 4:全局事件总线(EventBus)

  1. 原理:前端组件间通信的事件总线需要唯一实例,否则事件无法跨组件传递;

  2. 示例:

    class EventBus {
        static #instance = null;
        constructor() {
            this.events = new Map();
        }
        static getInstance() {
            if (!EventBus.#instance) {
                EventBus.#instance = new EventBus();
            }
            return EventBus.#instance;
        }
        on(event, callback) {
            if (!this.events.has(event)) {
                this.events.set(event, []);
            }
            this.events.get(event).push(callback);
        }
        emit(event, data) {
            if (this.events.has(event)) {
                this.events.get(event).forEach((cb) => cb(data));
            }
        }
    }
    
    // 组件A订阅事件
    EventBus.getInstance().on("user-change", (data) => console.log(data));
    // 组件B发布事件(全局可触发)
    EventBus.getInstance().emit("user-change", { name: "张三" });
    

单例模式优缺点

优点

  1. 唯一性保证:确保一个类只有一个实例;

  2. 全局访问点:提供统一的访问入口;

  3. 延迟加载:懒汉式实现可以延迟实例化;

  4. 避免重复创建:节省系统资源;

  5. 状态共享:多个模块可以共享同一个状态;

缺点

  1. 违反单一职责:同时负责创建和管理实例;

  2. 难以扩展:继承困难,测试不便;

  3. 隐藏依赖:全局状态可能导致耦合;

  4. 并发问题:多线程环境下需要特殊处理;

  5. 生命周期管理:实例常驻内存,不易销毁;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 单例模式核心解析
  2. 2. 常用使用场景
    1. 2.1. 场景 1:全局状态管理(如 Vuex/Pinia 核心、自定义全局存储)
    2. 2.2. 场景 2:弹窗 / 模态框(Modal/Dialog)
    3. 2.3. 场景 3:全局缓存管理
    4. 2.4. 场景 4:全局事件总线(EventBus)
  3. 3. 单例模式优缺点
    1. 3.1. 优点
    2. 3.2. 缺点