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

发布订阅者模式是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')
上一篇 : 日志监控下一篇 : 手写call,apply,bind方法