# EventEmitter
PubSub 模式,是 Publish/Subscribe 的缩写,意为“发布/订阅”模式。
方法:
- addListener(event, listener) | on(event, listener):添加一个事件监听器。
- removeListener(event, listener) | off(event, listener):从一个事件中移除一个监听器。
- once(event, listener):添加一个只会被触发一次的监听器。
- emit(event, ...args):手动触发一个事件,并传递可选参数。
- listeners(event):返回指定事件的监听器数组。
- listenerCount(event): 返回指定事件的监听器数量。
属性:
- EventEmitter.defaultMaxListeners: 默认设置的最大监听器数量,默认值为 10。
- emitter.eventNames():返回所有的事件列表。
- emitter.getMaxListeners():获取最大监听器数量。
- emitter.listenerCount(type):获取事件的监听器数量。
- emitter.rawListeners(event):返回所有监听该事件的函数。
实现一个EventEmitter (opens new window)
https://codesandbox.io/s/event-emitter-yvhm69?file=/src/index.js
/**
* @description:
* @param {*} handles 存储 event 类型的 click、dblclick、change等
*/
export default class PubSub {
constructor() {
this.handlers = {}
}
// 注册事件监听器
/**
* @description: 注册事件监听器
* @param {*} type 监听的事件类型
* @param {*} listener 回调函数
*/
addListener(type, listener) {
if (!(type in this.handlers)) {
// handlers内不存在,就存储
this.handlers[type] = []
}
// 将对应的事件处理函数存入 handlers[type]中
this.handlers[type].push(listener)
}
// 移除事件监听器
removeListener(type) {
if (type in this.handlers) {
const length = this.handlers[type].length
if (length === 1) {
delete this.handlers[type]
}
}
}
emit(type) {
// args 为回调函数传入的参数
// 截取传入的事件处理函数
const args = [...arguments].slice(1)
// 获取事件处理函数数组
const eventList = this.handlers[type]
/**
* 循环遍历触发handlers中对应type的所有事件处理函数
* for (const item of this.handlers[type])
* item()
* args 为事件处理函数传入的参数的集合:Array
*/
// 如果 handlers 中存在该事件类型
if(eventList) {
// 循环遍历触发 handlers 中对应 type 的所有事件处理函数
for (const item of eventList) {
// item(args)
item.apply(this, args)
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
触发
import EventEmitter from './eventemitter.mjs'
const emitter = new EventEmitter()
const fn1 = function (val) {
console.log('fn1', val)
}
const fn2 = function (val) {
console.log('fn2', val)
}
emitter.addListener('message', fn1)
emitter.addListener('message', fn2)
emitter.emit('message', 'test1') // fn1 test1 fn2 test2
2
3
4
5
6
7
8
9
10
11
在removeListener中,为什么我们不直接delete this.events[event] 来删除注册的事件呢?
因为这样做可能会导致一些潜在的问题,在 EventEmitter 中,通过 on 方法注册的事件回调函数保存在了 this.events[event] 数组中,如果我们直接使用 delete 关键字删除 this.events[event] 对象的属性,虽然该属性会被删除,但是保存在数组中的回调函数仍然存在。
这种情况下,有两种解决方案:
- 使用数组的
splice方法从数组中删除指定的回调函数; - 重新分配一个新的空数组作为
this.events[event]对象的值,这样既可以删除已有的回调函数,也不会影响到其他属性。
在 EventEmitter 中,我们选择使用第一种方案,即使用 splice 方法从数组中删除指定的回调函数。这种方案不会创建新的数组对象,也不会影响到其他属性,所以比较安全。
# EventTarget.removeEventListener()
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/removeEventListener
removeEventListener(type, listener);
removeEventListener(type, listener, options);
removeEventListener(type, listener, useCapture);
2
3
removeEventListener不能用来移除事件,该方法是用来删除事件处理函数的,其中第二个参数listener是必须的
# dispatchEvent手动触发事件
dispatchEvent() 方法会向一个指定的事件目标派发一个 Event (opens new window),并以合适的顺序(同步地)调用所有受影响的 EventListener
// 获取要触发的 DOM 节点
const myButton = document.getElementById('my-button');
// 创建事件对象
const myEvent = new Event('click');
// 将事件触发到 DOM 节点中
myButton.addEventListener('click', function(event) {
console.log('Button clicked!');
});
myButton.dispatchEvent(myEvent);
2
3
4
5
6
7
8
9
customEvent自定义事件
interface MyEventData {
message: string
}
const ref = useRef<HTMLDivElement>(null)
const myCustomEvent = new CustomEvent<MyEventData>('my-event', {
detail: 'HelloWorld'
})
useEffect(() => {
ref.current?.addEventListener('my-event', (e: Event) => {
const event = e as CustomEvent<MyEventData>
console.log(event.detail)
})
ref.current?.dispatchEvent(myCustomEvent) // 手动触发
}, [])
return <div ref={ref}>click me</div>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
看到这里的detail,突然就想到了小程序,小程序也有个e.detail
相关信息可以看小程序triggerEvent
← 同步和异步与宏任务和微任务 fetch →