JS 实现监听器Listener类


前言

​ 几天前不是谈到了事件总线eventBus吗, 然后对里面的监听器相关知识挺感兴趣。今天我用代码简单实现了下,大家可以看看是否有需要改进的地方。可以跳转到本文文末查看我的最终代码。

要求

​ 实现一个监听器对象Listener类,需要具有四个方法:

  • on(event: String, fn: Function([data[, callback])]) 对事件event绑定一个监听器fn
  • once(event: String, fn: Function) 对事件event绑定一个只执行一次的监听器fn
  • off(event: String [, fn: Function] )
    • 只有第一个参数时,对事件event解绑所有监听器
    • 有两个参数时, 对事件event解绑一个监听器fn (这里的fn需要与之前使用on函数绑定的fn指向同一个对象, 所以注意使用匿名函数做监听器函数的情况 参考eventBus一节中提到的匿名函数问题)
  • trigger(event: String, data: Object, callback: Function) 触发事件event绑定的所有监听器,并传递参数data及回调函数callback (回调函数需要在on绑定的函数fn中自定义执行位置)

思路及代码

​ 准备用一个Map来维护事件名与监听器列表的对应关系

​ 注意在off函数中有两种参数形式,以及在trigger函数中注意移除一次性的监听器函数,代码如下:

// 判断是否为函数
function isFunction(fn) {
  return typeof fn === 'function'
}

// 监听器类
function Listener() {
  // 监听器Map, 维护事件名与监听器列表的对应关系
  this.map = new Map();

  /**
   * 添加一个监听器
   * (并没有去重,相同的监听器函数可多次添加)
   * @param {string} name 事件名
   * @param {function} fn 监听器函数
   */
  this.on = function (name, fn) {
    if (isFunction(fn)) {
      const item = { once: false, fn };
      this.map.set(name, this.map.has(name) ? this.map.get(name).push(item) : [item]);
    } else {
      throw new Error('请绑定函数')
    }
  }

  /**
   * 添加一个只执行一次的监听器
   * @param {string} name 事件名
   * @param {function} fn 监听器函数
   */
  this.once = function (name, fn) {
    if (isFunction(fn)) {
      const item = { once: true, fn };
      this.map.set(name, this.map.has(name) ? this.map.get(name).push(item) : [item]);
    } else {
      throw new Error('请绑定函数')
    }
  }

  /**
   * 移除监听器
   * 若无第二个参数fn, 则移除所有监听器
   * @param {string} name 事件名
   * @param {function} fn 监听器函数
   */
  this.off = function (name, fn) {
    if (this.map.has(name)) {
      if (!fn) {
        this.map.delete(name);
      } else if (isFunction(fn)) {
        let items = this.map.get(name);
        let index = items.findIndex(item => item.fn === fn);
        if (index > -1) {
          items.splice(index, 1);
        }
      } else {
        throw new Error('请解绑函数')
      }
    }
  }

  /**
   * 触发监听器
   * @param {string} name 事件名
   * @param {object} data 传递参数
   * @param {function} callback 回调函数
   */
  this.trigger = function (name, data, callback) {
    if (this.map.has(name)) {
      let items = this.map.get(name);
      for (let len = items.length, i = len - 1; i >= 0; i--) {
        let { once, fn } = items[i];
        fn(data, callback);
        if (once) {
          items.splice(i, 1); // 移除只执行一次的监听器
        }
      }
    }
  }
}



// 测试
let l = new Listener();
l.on('event1', (data) => { // 每次使用trigger函数触发都会执行一次
  console.log('event1 on', data);
})
// l.once('event1', (data) => { // 只执行一次,之后不再执行(已被移除)
//  console.log('event1 once', data);
// })
l.trigger('event1', 1) // event1 1
l.trigger('event1', 2) // 不执行
l.trigger('event1', 3) // 不执行

let fn = (data, callback) => { // 定义监听器函数fn
  console.log(data);
  callback(data);
}
l.on('event2', fn)
console.log(l, l.map);
l.trigger('event2', 1, (res) => { // 传入数据 1, 并执行回调函数
  console.log('callback1', res * 2); // callback1 2
})
l.off('event2', fn); // 移除事件‘event2’提仅有的监听器函数fn, 所以之后的trigger函数无法正确执行
l.trigger('event2', 2, (res) => { // 传入数据 2, 并执行回调函数
  console.log('callback2', res * 3); // callback1 6
})
console.log(l, l.map);

文章作者: hjwforever
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hjwforever !
评论
  目录