“单一实例”(Single Instance)是一个编程概念,通常与单例模式(Singleton Pattern)相关,指的是在整个应用程序中,一个类或对象只存在一个实例。无论在程序的哪里访问这个实例,始终得到的是同一个对象,而不是创建多个不同的对象。
1. 单一实例的定义
- 单一实例:一个类或对象的实例在应用程序生命周期中只被创建一次,之后的所有引用都指向这个唯一的实例。
- 单例模式:一种设计模式,确保类只有一个实例,并提供一个全局访问点。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class EventBus { constructor() { this.events = {}; } on(eventName, callback) { if (!this.events[eventName]) this.events[eventName] = []; this.events[eventName].push(callback); } emit(eventName, data) { if (this.events[eventName]) this.events[eventName].forEach(cb => cb(data)); } } // 创建单一实例 const instance = new EventBus(); export default instance; |
- 在这个例子中,
instance
是EventBus
的唯一实例,任何导入它的模块都会使用同一个对象。
2. 如何实现单一实例?
在 JavaScript 中,有几种常见的方式实现单一实例:
(1)模块导出
在现代 JavaScript(尤其在 Node.js 或前端模块化开发中),通过模块系统天然支持单一实例:
1 2 3 4 5 6 7 |
// eventBus.js const eventBus = { events: {}, on(eventName, callback) { /* ... */ }, emit(eventName, data) { /* ... */ } }; export default eventBus; |
- 每次
import eventBus from './eventBus.js'
时,得到的都是同一个eventBus
对象,因为模块只会被加载一次,导出的是固定的引用。
(2)全局变量
在浏览器环境中,可以通过全局对象(如 window
)定义单一实例:
1 2 3 4 |
window.eventBus = { events: {}, on(eventName, callback) { /* ... */ } }; |
- 任何地方都可以通过
window.eventBus
访问同一个实例。
(3)类中的静态实例
通过类的静态方法或属性控制实例创建:
1 2 3 4 5 6 7 8 9 10 11 |
class EventBus { static instance = null; constructor() { if (EventBus.instance) return EventBus.instance; // 如果已有实例,直接返回 this.events = {}; EventBus.instance = this; // 保存单一实例 } } const bus = new EventBus(); // 第一次创建 const bus2 = new EventBus(); // 返回同一个实例 console.log(bus === bus2); // true |
3. 在你的场景中为什么是单一实例?
在你的代码中:
1 2 3 4 5 6 7 8 9 10 11 |
<template> <div> <event-bus-a /> <event-bus-b /> </div> </template> <script setup lang="ts"> import EventBusA from './components/EventBusA.vue'; import EventBusB from './components/EventBusB.vue'; </script> |
假设 EventBusA
和 EventBusB
都导入了同一个 eventBus
:
1 2 3 |
// EventBusA.vue import eventBus from '../eventBus'; eventBus.emit('message', 'Hello'); |
1 2 3 |
// EventBusB.vue import eventBus from '../eventBus'; eventBus.on('message', msg => console.log(msg)); |
eventBus
是单一实例:因为../eventBus'
文件只导出了一个对象(例如export default new EventBus()
),而 JavaScript 模块系统会缓存这个导出结果。- 通信的关键:
EventBusA
和EventBusB
操作的是同一个eventBus
对象,它们的emit
和on
方法作用于同一个events
数据结构,因此可以互相感知事件。
4. 为什么单一实例能让组件通信?
- 共享状态:单一实例意味着所有组件访问的是同一个内存中的对象。如果
EventBusA
在eventBus.events
中添加了一个事件监听器,EventBusB
触发这个事件时会访问相同的events
对象。 - 事件分发:
emit
方法会遍历events
中对应事件名的回调函数,而这些回调可能是由不同组件注册的。只要实例相同,事件就能跨越组件传递。
例如:
EventBusA
调用eventBus.emit('msg', 'Hi')
。eventBus
检查events['msg']
中的回调,找到EventBusB
注册的函数并执行。- 因为
eventBus
是同一个实例,EventBusB
能收到消息。
如果 EventBusA
和 EventBusB
使用的是不同的 eventBus
实例(比如每次 new EventBus()
),它们的 events
对象是独立的,就无法通信。
5. 单一实例的优点和缺点
优点:
- 全局一致性:所有组件共享同一个状态或行为。
- 解耦通信:无需通过父组件或 props,组件间可以直接交互。
- 简单实现:在小项目中快速实现跨组件通信。
缺点:
- 难以维护:随着项目变大,事件名可能冲突,事件流难以追踪。
- 内存管理:如果不清理监听器(比如忘记调用
off
),可能导致内存泄漏。 - 替代方案:在现代框架中,通常推荐状态管理(如 Vuex/Pinia)替代全局单一实例的
eventBus
。
6. 总结
- 单一实例是指整个应用中只有一个对象实例,所有引用都指向它。
- 在你的例子中,
eventBus
是单一实例(很可能通过模块导出实现),因此EventBusA
和EventBusB
可以通过它通信。 - 通信原理:它们共享同一个
eventBus
,一个组件发布事件,另一个组件监听事件,事件总线负责在同一实例上分发。
简单来说,eventBus
就像一个“公共电话亭”,因为它只有一个(单一实例),所以任何一个组件“打电话”(emit),其他组件都能“听到”(on)。