手写一个简单的发布订阅者模式

发布订阅者模式是JavaScript的一种设计模式,比较常用的场景就是 document的dom元素监听事件,于是就尝试手写了一个简单的发布订阅者模式

定义一个EventUtils类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default class EventUtils {
/**
* 键值对 对应事件名称以及数组的值
*/
static handler: {};
/**
* on 方法 添加监听事件
*/
static on(name: string, handler: Function): typeof EventUtils;
/**
* off 方法 移除监听事件
*/
static off(name: string, handler: Function): typeof EventUtils;
/**
* emit 方法 触发监听的事件
*/
static emit(name: string, ...args: any): typeof EventUtils;
/**
* once 方法 添加事件 只会被执行一次
*/
static once(name: string, handler: Function): void;
}

handler 存放订阅事件数据信息

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
// 订阅者注册事件信息
// click 举例
{
'click': [{
funciton () {
console.log('click event')
}
}, {
function (t) {
console.log(`click event t: ${t}`)
}
}], {
// ...
}
}

// 数据结构
interface IHandler {
// 回调方法
fn: Function,
// 类型 on once 区分
type: string,
// 事件名称
name: string
}

EventUtils.on 方法添加订阅事件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static on (name: string, handler: Function) {
const i:IHandler = {
fn: handler,
type: 'on',
name: name
}
// 判断是否已经有这个事件订阅
// 有就直接push
if (Object.keys(EventUtils.handler).includes(name)) {
EventUtils.handler[name].push(i)
return EventUtils
}
// 没有就创建一个且添加到数组中
EventUtils.handler[name] = [].concat(i)
return EventUtils
}

EventUtils.once 方法添加一次事件订阅,执行之后取消事件订阅

1
2
3
4
5
// once 方法 添加事件 只会被执行一次
static once (name: string, handler: Function) {
EventUtils.on(name, handler)
EventUtils.handler[name][0]['type'] = 'once'
}

EventUtils.off 取消事件订阅

1
2
3
4
5
6
7
8
9
10
11
static off (name: string, handler: Function) {
const event: any[] = EventUtils.handler[name]
if (event) {
for (let i = event.length - 1; i >= 0; i--) {
if (event[i].fn === handler) {
event.splice(i, 1)
}
}
}
return EventUtils
}

EventUtils.emit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static emit (name: string, ...args: any) {
const event = EventUtils.handler[name]
let newEvent = []
event && event.length && event.forEach((item: IHandler, index: number) => {
item.fn.call(this, ...args)

// 如果有只监听一次的事件
if (item.type !== 'once') {
newEvent.push(event.slice(index, index + 1))
}
})

const hasOnce = event && event.length && event.some((item: IHandler) => {
return item.type === 'once'
})

if (hasOnce) {
EventUtils.handler[name] = newEvent
}

// 这里做一个执行完成之后的 once代码 off 的操作
return EventUtils
}

代码执行

1
2
3
4
5
6
7
8
9
10
11
12
13
const handle = function () {
console.log('test 触发')
}

const handle1 = function (i) {
console.log('test1 触发,参数:', i)
}

EventUtils.on('test', handle)
EventUtils.once('test1', handle1)

EventUtils.emit('test')
EventUtils.emit('test1', 'hahaha')