发布订阅
步骤
- 通过
button.addEventListener('click', fn)监听/订阅事件 - 用户点击触发
button.dispatchEvent(...'click')执行,即触发事件 fn被调用
提问
- 如何取消监听/订阅?-
button.removeEventListener - 如果多次监听会怎样?- 按监听顺序依次执行
fn1、fn2 - 如果将第 1、2 步颠倒过来会怎样?- 会使得
fn不被执行 button是怎么得到addEventListener方法的?- 通过原型链得到的
const button = document.getElementById('x')
const fn = () => console.log('hi')
//监听 click 事件
//fn 叫做事件处理函数
button.addEventListener('click', fn)
//取消监听
button.removeEventListener('click', fn)
setTimeout(() => {
//button.click()
//触发事件
//派发事件
//发射事件
button.dispatchEvent(new Event('click'))
}, 5000)
实现发布订阅
第一次尝试(不考虑事件名)
class EventEmitter {
constructor() {
this.queue = []
}
addEventListener(fn) {
this.queue.push(fn)
}
removeEventListener(fn) {
const index = this.queue.indexOf(fn)
if (index < 0) return //找不到就不用删了
this.queue.splice(index, 1)
}
dispatchEvent() {
//调用时可能不止一个fn
this.queue.forEach(fn => fn())
}
}
const obj = new EventEmitter()
const fn = () => console.log('hi')
obj.addEventListener(fn)
// obj.removeEventListener(fn)
setTimeout(() => {
obj.dispatchEvent()
}, 5000);
ajax()//会在未来调用api.dispatchEvent
第二次尝试(考虑事件名)
class EventEmitter {
constructor() {
this.queue = { /* xxx: [] 无法初始化 */ }
}
addEventListener(name, fn) {
this.queue[name] = this.queue[name] || [] // 初始化
this.queue[name].push(fn)
}
dispatchEvent(name) {
// 可能直接执行派发事件,没有监听,可能没有初始化
// 防御性编程
// if (this.queue[name] === undefined) { return }
this.queue[name]?.forEach(fn => fn())
}
removeEventListener(name, fn) {
if (this.queue[name] === undefined) { return }
const index = this.queue[name].indexOf(fn)
if (index >= 0) {
this.queue[name].splice(index, 1)
}
}
}
const obj = new EventEmitter()
const fn = () => console.log('hi')
obj.addEventListener('xxx', fn)
// obj.removeEventListener(fn)
setTimeout(() => {
obj.dispatchEvent('xxx')
}, 5000);
第三次尝试(考虑fn的参数)
class EventEmitter {
constructor() {
this.queue = { /* xxx: [] 无法初始化 */ }
}
addEventListener(name, fn) {
this.queue[name] = this.queue[name] || [] // 初始化
this.queue[name].push(fn)
}
dispatchEvent(name, ...args) {
this.queue[name]?.forEach(fn => fn.call(undefined, ...args))
}
removeEventListener(name, fn) {
if (this.queue[name] === undefined) { return }
const index = this.queue[name].indexOf(fn)
if (index >= 0) {
this.queue[name].splice(index, 1)
}
}
}
const obj = new EventEmitter()
const fn = (...args) => console.log(...args)
obj.addEventListener('xxx', fn)
// obj.removeEventListener(fn)
setTimeout(() => {
obj.dispatchEvent('xxx', '饿死了', '烦死了')
}, 5000);
总结
发布订阅模式的特点
- api 提供了
addEventListener / on / subscribe - api 提供了
dispatchEvent / emit / trigger - api 提供了
removeEventListener / off / unsubscribe - 满足上述条件的对象被称为
EventEmitter,实现了发布订阅模式
本质
- 把 回调(函数) 放在 队列(数组) 里,等待被逐个调用
发布订阅解决了什么问题?
- 所有异步任务都可以用发布订阅来管理
- 先订阅成功事件和失败事件
xhr.on('load'); xhr.on('error') - 再在任务完成时触发成功或失败事件
xhr.emit('load'); xhr.emit('error') - 是一个通用的异步任务管理方案
缺点是什么?
事件少时还行;但当事件过多时,很难管理