基于mitt实现的进阶版发布-订阅模式

262 阅读4分钟
  1. mitt API方法有:
  • on 订阅事件;
  • emit 发布事件;
  • off 取消订阅的事件。

本质上就是一个Map对象,它用于存储订阅事件。

看代码前想象Map的数据结构:key—value

import mitt from 'mitt';


// 创建一个事件管理器
const emitter = mitt();


// 注册一个事件
emitter.on('click', (evt) => {
  console.log('clicked', evt.target);
});

// 再注册一个事件
emitter.on('click', (evt) => {
  console.log('clicked', evt.target2);
});


// 触发一个事件
emitter.emit('click', { target: '我是粽叶', target2: '我是粽叶二号' });


// 取消一个事件
emitter.off('click');

运行一下:

image.png

2.去除掉TypeScript类型,将其修改成javaScript实现(对照使用例子来理解各个方法):

export default function mitt(all){
  //使用Map存储注册的事件
  all = all || new Map();

  return {
    all,
	//on(type, handler) {...}:注册一个事件,
  //接受两个参数:type表示事件类型,handler表示事件处理函数。
  //如果已经存在该类型的事件,则将该处理函数添加到已有的处理函数列表中,否则创建一个新的处理函数列表。
    on(type, handler) {
      const handlers= all!.get(type);
      if (handlers) {
        handlers.push(handler);
      }
      else {
        all!.set(type, [handler])
      }
    },

    //off(type, handler) {...}:取消注册一个事件,
    //接受两个参数:type表示事件类型,handler表示事件处理函数。
    //如果存在该类型的事件,则从已有的处理函数列表中删除该处理函数,
    //如果没有传入handler参数,则删除该类型的所有处理函数。
    off(type, handler) {
      const handlers = all!.get(type);
      if (handlers) {
        if (handler) {
          const index =  handlers.indexOf(handler)
          handlers.splice(index > -1 ? index : 0, 1);
        }
        else {
          all!.set(type, []);
        }
      }
    },

    //emit(type, evt) {...}:触发一个事件
    emit(type, evt) {
      let handlers = all!.get(type);
      if (handlers) {
        handlers.slice().map((handler) => {
            handler(evt);
          });
      }
      //判断是否存在"*" 订阅的事件,"*"注册的事件进行兜底
      handlers = all!.get('*');
      if (handlers) {
        handlers.slice().map((handler) => {
            handler(type, evt);
          });
      }
    }
  };
}

在emit方法中为什么要handlers.slice().map()?

下面给出一个例子来证明为什么要写handlers.slice():

假设有一个事件类型myEvent,已经注册了两个处理函数handler1和handler2,并且在执行handler1时会删除handler2。

如果直接使用原列表handlers,则在执行handler2时会出现问题,因为此时handler2已经被删除了。

为了避免这种情况,所以需要先复制一份处理函数列表,即使删除了原数组当中的某个值那和我复制的有啥关系?我继续在复制的列表上依次执行处理函数。具体代码如下:

const handlers = [handler1, handler2];
handlers.slice().map((handler) => {
  handler();
});

以大家的水平理解起来just so so~

这样我们就学会了一个拥有9k Star库的所有源码。(完结撒花)

.

.

.

.

.

.

.

.

但是我们能止步于此嘛?我们已经站在了mitt的肩膀上看到了新的风景,那为什么我们不更进一步?

1.观察源码发现这些代码都是同步的,那假如我注册的一个事件是异步(例如一个请求)怎么办?

在emit方法中,将处理函数列表改为异步函数列表,即将所有处理函数改为异步函数,例如:

const asyncHandlers = handlers.map(async (handler) => {
  await handler(evt);
});

2.事件优先级:在on方法中,可以新增一个可选参数priority,表示事件处理函数的优先级,优先级越高的处理函数会先执行。在emit方法中,可以按照优先级从高到低依次执行处理函数。

on(type, handler, priority = 0) {
  const handlers = all!.get(type) || [];
  handlers.push({handler, priority});
  //按照优先级从高到低
	handlers.sort((a, b) => b.priority - a.priority);
  all!.set(type, handlers);
},

3.事件监听器函数:在on方法中,可以新增一个可选参数listener,表示事件监听器,用于监听事件的触发情况。在emit方法中,可以在执行处理函数前先执行监听器,例如记录事件触发次数、事件触发时间等信息。

on(type, handler, priority = 0, listener) {
  const handlers = all!.get(type) || [];
  handlers.push({handler, priority});
  handlers.sort((a, b) => b.priority - a.priority);
  all!.set(type, handlers);
  if (listener) { //当注册的时候判断是否传入
    listener({type, handler, priority});
  }
},
emit(type, evt, listener) {
  if (listener) {
    listener({type, evt});
  }
  let handlers = all!.get(type);
  if (handlers) {
    handlers.slice().map((item) => {
        item.handler(evt);
      });
  }
}

具体使用:

function eventListener({type, handler, priority}) {
  console.log(`事件 ${type} 已注册,处理函数为 ${handler},优先级为 ${priority}`);
}


function eventTriggerListener({type, evt}) {
  console.log(`事件 ${type} 已触发,事件对象为 ${evt}`);
}


const bus = mitt();


bus.on('click', () => console.log('点击事件已触发'), 1, eventListener);
bus.emit('click', {target: '按钮'}, eventTriggerListener);

完整代码:

export default function mitt(all){
  //使用Map存储注册的事件
  all = all || new Map();


  return {
    all,
    on(type, handler, priority = 0, listener) {
      const handlers = all!.get(type) || [];
      handlers.push({handler, priority});
      handlers.sort((a, b) => b.priority - a.priority);
      all!.set(type, handlers);
      if (listener) {
        listener({type, handler, priority});
      }
    },
    
    off(type, handler) {
      const handlers = all!.get(type);
      if (handlers) {
        if (handler) {
          const index =  handlers.findIndex(item => item.handler === handler);
          handlers.splice(index > -1 ? index : 0, 1);
        }
        else {
          all!.set(type, []);
        }
      }
    },
    
    emit(type, evt, listener) {
      if (listener) {
        listener({type, evt});
      }
      let handlers = all!.get(type);
      if (handlers) {
        handlers.slice().map(async (handler) => {
            await handler(evt);
          });
      }
    }
  };
}


function eventListener({type, handler, priority}) {
  console.log(`事件 ${type} 已注册,处理函数为 ${handler},优先级为 ${priority}`);
}


function eventTriggerListener({type, evt}) {
  console.log(`事件 ${type} 已触发,事件对象为 ${evt}`);
}


const bus = mitt();


bus.on('click', () => console.log('点击事件已触发'), 1, eventListener);
bus.emit('click', {target: '按钮'}, eventTriggerListener);

放在代码中运行:

输出结果:

image.png