一、概述
发布订阅模式(Publish-Subscribe Pattern)是一种经典的设计模式,核心是解耦事件的发布者和订阅者,使两者无需直接依赖即可完成通信。本文档基于 TypeScript 实现了支持单页面通信的 EventEmitter,并提供完整的使用指南、API 说明和最佳实践。
核心优势
二、核心功能
| on(name, fn) | 订阅事件(持久) |
| once(name, fn) | 订阅一次(执行后自动销毁) |
| emit(name, …args) | 触发事件,传参 |
| off(name?, fn?) | 取消订阅 |
| listenerCount(name) | 获取监听数 |
| eventNames() | 获取所有事件名 |
三、完整代码
/**
* 事件发射器核心类
* 实现事件的订阅、取消订阅、触发、一次性订阅等功能
*/
class EventEmitter {
/* 存储事件名与对应回调函数数组的映射,增强类型约束 */
private events:{[key:string]:Function[]}={}
/* 订阅事件 */
on(eventName:string ,callback:Function):this {
if(!this.events[eventName]){
this.events[eventName] = []
}
this.events[eventName].push(callback)
return this
}
/* 一次性订阅 */
once(eventName:string,callback:Function):this{
const onceCallback = (…args:any[])=>{
callback(…args)
this.off(eventName,onceCallback)
}
return this.on(eventName,onceCallback)
}
/* 取消订阅 */
off(eventName?:string ,callback?:Function):this{
if(!eventName){
/* 清除所有事件 */
this.events = {}
return this
}
if(!callback){
/* 清除指定事件的所有回调 */
delete this.events[eventName]
return this
}
/* 移除指定回调 */
const callbacks = this.events[eventName]
if(callbacks){
const index = callbacks.indexOf(callback)
if(index>-1){
callbacks.splice(index,1)
}
}
return this
}
/* 触发事件 */
emit(eventName:string,…args:any[]):this{
const callbacks = this.events[eventName]
if(callbacks){
callbacks.forEach(callback=>{
try {
callback(…args)
} catch (error) {
console.log(`Error in event ${eventName}:`,error)
}
})
}
return this
}
/* 获取事件监听器数量 */
listenerCount(eventName:string):number{
return this.events[eventName]?.length||0
}
/* 获取所有事件名 */
eventNames():string[]{
return Object.keys(this.events)
}
}
/* eventCenter (单例 + DOM风格API)*/
class EventCenter extends EventEmitter {
addEventListener(eventName:string,callback:Function):this{
return this.on(eventName,callback)
}
removeEventListener(eventName:string,callback?:Function):this{
return this.off(eventName,callback)
}
dispatchEvent(eventName:string,data?:any):this{
return this.emit(eventName,data)
}
}
/* 创建单例实例 */
const eventCenter = new EventCenter()
export default eventCenter
使用示例:
import eventCenter from './simple-event-center'
// 订阅事件
const handler = (data) => {
console.log('收到消息:', data)
}
eventCenter.addEventListener('message', handler)
// 触发事件
eventCenter.dispatchEvent('message', { text: 'Hello World' })
// 一次性订阅
eventCenter.once('once-event', (data) => {
console.log('只执行一次:', data)
})
// 取消订阅
eventCenter.removeEventListener('message', handler)
四、注意点
本方法在跨浏览器标签页 / 窗口无法直接正常使用,仅支持单页面使用
核心原因:每个浏览器标签页 / 窗口都是独立的 JavaScript 执行环境,你的 eventCenter 是单个页面内的单例对象,不同页面的 eventCenter 内存空间完全隔离 ——A 页面发布的事件,B 页面的 eventCenter 根本接收不到。
举个直观例子:
- 标签页 1:eventCenter.emit('user:login', {name: '张三'}) → 只有标签页 1 内的订阅者能收到;
- 标签页 2:eventCenter.on('user:login', (data) => console.log(data)) → 永远收不到标签页 1 的事件。
想要实现跨页面通信:
| 1. localStorage + storage 事件 | localStorage.setItem + window.addEventListener('storage') | 同域名下的跨标签页 | 简单、无需处理跨域、兼容性好 | 只能同域名、数据存在本地 |
| 2. postMessage | window.postMessage | 跨域名 /iframe/ 窗口 | 支持跨域、灵活 | 需要获取目标窗口的引用 |
![打卡信奥刷题(3676)用C++实现信奥题 P12271 [蓝桥杯 2024 国 Python B] 括号与字母-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261107050125-6aeeb0a5a5ade-220x150.png)




