js实现简单版的EventBus实例

1
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
在vue中,可以利用EventBus来派发和接收事件

# eventBus.js:
import Vue from 'vue';
let eventHub = new Vue();
export default eventHub;

# a.vue
import eventBus from '../js/eventBus'
eventHub.$on('event', (data) => {})

# b.vue
import eventBus from '../js/eventBus'
eventHub.$emit('event', data);

设计模式:
订阅者发布者模式(也称观察者模式),这种设计模式在前端很常见。一般来说:先订阅(on),再发布(emit)
DOM事件也是一个订阅发布模式
订阅:DOM.addEventListener('click', function () { })
发布:一个点击事件

如何手动实现简单版的EventBus实例:
API的设计:
on('event', fn) 订阅消息(event: 订阅的消息名称、fn: 订阅的消息)
once('event', fn) 仅订阅一次消息,一旦被执行立即销毁
emit('event', msg) 发布消息(event: 消息名称、msg: 发布的消息)
off('event') 移除消息(不传参数: 销毁所有消息)

参考:Vue $on api

1
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
// es5实现eventBus实例

// 简易版如下:
function EventBusClass () {
this.eventObj = {} // 管理事件
}
EventBusClass.prototype = {
// 订阅消息
on: function (event, fn) {
const vm = this
this.eventObj[event] = fn
return vm
},
// 订阅消息,派发一次后即销毁
once: function (event, fn) {
const vm = this
// 思考:如何在执行完fn之后销毁该event呢,需要重新构造一个新的fn
function fn2 () {
fn.apply(vm, arguments) // 如果使用fn(),则无法接收参数
// delete this.eventObj[event] // 重复性处理,建议直接使用写好的方法
vm.off(event)
}
// this.eventObj[event] = fn2
vm.on(event, fn2)
return vm
},
// 发布消息
emit: function (event, msg) {
const vm = this
// 未订阅的忽略
if (!this.eventObj.hasOwnProperty(event)) return vm
this.eventObj[event](msg)
return vm
},
// 销毁消息(不传销毁所有)
off: function (event) {
const vm = this
if (event === undefined) {
this.eventObj = {}
return vm
}
// 未订阅的忽略
if (!this.eventObj.hasOwnProperty(event)) return vm
delete this.eventObj[event]
return vm
}
}

ES6实现按照下列结构改造即可

1
2
3
4
5
6
7
8
9
10
11
12
13
class EventBusClass2 {
constructor () {
this.eventObj = {}
}
on (event, fn) {
}
once (event, fn) {
}
emit (event, msg) {
}
off (event) {
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
<button onClick="on_a()">订阅A事件</button>
<button onClick="on_b()">订阅B事件</button>
<br>
<button onClick="once_a()">订阅A事件一次</button>
<button onClick="once_b()">订阅B事件一次</button>
<br>
<button onClick="emit_a()">派发A事件</button>
<button onClick="emit_b()">派发B事件</button>
<br>
<button onClick="off()">销毁A</button>
<button onClick="off_all()">销毁所有</button>
<br>
<button onClick="chain()">链式调用:订阅A事件,派发A事件</button>

1
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
const EventBus = new EventBusClass()
// 订阅
function on_a () {
EventBus.on('click', (data) => {
console.log(data)
})
}
function on_b () {
EventBus.on('dbClick', (data) => {
console.log(data)
})
}
// 订阅一次
function once_a () {
EventBus.once('click', (data) => {
console.log(data)
})
}
function once_b () {
EventBus.once('dbClick', (data) => {
console.log(data)
})
}
// 发布
function emit_a () {
EventBus.emit('click', {a: 1})
}
function emit_b () {
EventBus.emit('dbClick', {b: 1})
}
// 销毁
function off () {
EventBus.off('click')
}
function off_all () {
EventBus.off()
}
// 链式调用
function chain () {
EventBus
.on('mouseup', (data) => {
console.log(data)
})
.emit('mouseup', {c: 1})
}

以上只实现了一个简易版的EventBus,更多的参数支持和复杂情况未作处理,详细实现可查看Vue源码 eventsMixin方法