实现简单的事件订阅模块,核心的on、emit、off实现即可
(最强前端学习资料|已过字节、阿里、百度等大厂,一站式前端知识库,免费开源,辛苦Star)
function Event() {
// 键=事件名(字符串),值=回调函数数组
const events = new Map();
return {
on(eventName, callback) {
if (typeof eventName !== 'string' || eventName.trim() === '') {
throw new Error('事件名必须是非空字符串');
}
if (typeof callback !== 'function') {
throw new Error('回调函数必须是Function类型');
}
if (!events.has(eventName)) {
events.set(eventName, []);
}
events.get(eventName).push(callback);
},
emit(eventName, data) {
// 事件不存在则直接返回
if (!events.has(eventName)) return;
// 遍历回调列表执行(浅拷贝数组,避免执行中修改列表导致异常)
const callbacks = [...events.get(eventName)];
callbacks.forEach((callback) => {
// 容错:防止个别回调报错导致后续回调无法执行
try {
callback(data);
} catch (error) {
console.error(`事件${eventName}的回调执行失败:`, error);
}
});
},
off(eventName, callback) {
if (!events.has(eventName)) return;
const callbacks = events.get(eventName);
if (typeof callback === 'function') {
// 若传入回调,过滤掉匹配的函数
const newCallbacks = callbacks.filter((cb) => cb !== callback);
if (newCallbacks.length > 0) {
events.set(eventName, newCallbacks);
} else {
// 过滤后无回调,删除该事件(释放内存)
events.delete(eventName);
}
} else if (callback === undefined) {
// 若未传入回调,直接删除整个事件(取消所有订阅)
events.delete(eventName);
} else {
throw new Error('取消订阅时,回调参数必须是Function或undefined');
}
},
/**
* @param {string} eventName - 事件名称
* @returns {boolean} 是否存在订阅
*/
has(eventName) {
return events.has(eventName);
},
/**
* @param {string} eventName - 事件名称
* @returns {number} 订阅数量(事件不存在则返回0)
*/
getListenerCount(eventName) {
return events.has(eventName) ? events.get(eventName).length : 0;
}
};
}
const myEvent = Event();
function handleMessage(data) {
console.log('收到消息:', data);
}
myEvent.on('message', handleMessage);
myEvent.on('message', (data) => console.log('匿名回调收到:', data));
myEvent.emit('message', 'Hello EventEmitter!');
console.log('message事件订阅数:', myEvent.getListenerCount('message')); // 输出:2
myEvent.off('message', handleMessage);
myEvent.emit('message', '再次触发');
myEvent.off('message');
console.log('message事件是否存在:', myEvent.has('message'));
myEvent.emit('message', '不会触发');