Js Event

506 阅读3分钟

关于 event 菜狗子在这做点简单的整理下而已。 有错误欢迎指出斧正

node EventEmitter

在node中我们要实现一套事件机制真的是无比简单的。直接继承下原生的EventEmitter完成~。菜狗子比较闲,就和大家一起读读这部分的源码。

源码地址

/** 发布一个事件,这部分比较简单 */
EventEmitter.prototype.emit = function emit(type, ...args) {
  const events = this._events;
  /** 
   * 这里是一大段错误处理,附加错误信息的代码
   * 有几个辅助的函数,想了解建议看源码。
   * tips:同时会对`error`这个时间进行处理。(如果没有监听error的话)
   * 这里其实安利一个调试利器 
   *     原生函数:Error.captureStackTrace 挂载调用栈。
   *     以后获取调用栈就省的递归callee或者扔一个没用的错误了
   *     @link 
   */
  
  /** on 或者 addListener 的时候挂载的*/
  const handler = events[type];

  if (handler === undefined)
    // 这里可以看到哈,返回值决定了是否有被监听捕获,某些情况也许就用上饿了
    return false;
  if (typeof handler === 'function') {
   /** 这里效果和Function.prototype.apply一致 */
    Reflect.apply(handler, this, args);
  } else {
    /** 这个len能让程序稍稍快一点,比起每次循环都去求算。经常被忽略*/
    const len = handler.length;
    const listeners = arrayClone(handler, len);
    for (var i = 0; i < len; ++i)
      Reflect.apply(listeners[i], this, args);
  }

  return true;
};

监听一个事件,这部分其实也比较简单

/**
* 菜狗子手动注释TAT
* @param { EventEmitter } target event 实例
* @param { string } type 事件名称
* @pram { function } listener 回调事件,this指向当前event对象
* @pram { boolean } prepend 是否往前插入
*                   没想到吧人家支持插入在前面来解决事件调用顺序的问题
*                   但是on是往后插,插来插去的怎么感觉好污
*                   往前插入请用prependListener
*/
function _addListener(target, type, listener, prepend) {
  var m;
  var events;
  var existing;
  // 检查你给的监听器是否是函数
  checkListener(listener);

  events = target._events;
  if (events === undefined) {
    // Object.create 这个小技巧就不用介绍了吧,省内存。
    events = target._events = Object.create(null);
    // event 监听器是有限制的,以下是为啥要有限制的解释,就是方便排查内存问题
    // By default EventEmitters will print a warning if more than 10 listeners are
    // added to it. This is a useful default which helps finding memory leaks.
    target._eventsCount = 0;
  } else {
    // 这块 newListener 是很多其他模块有用到,比如https啥的。
    if (events.newListener !== undefined) {
      target.emit('newListener', type,
                  listener.listener ? listener.listener : listener);
      events = target._events;
    }
    existing = events[type];
  }
  
  // 这里我有些不理解为啥要用两种数据结构存监听器。。
  // 一个的时候是函数,多个的是才存成数组。感觉就是给自己挖坑啊!!
  if (existing === undefined) {
    // Optimize the case of one listener. Don't need the extra array object.
    events[type] = listener;
    ++target._eventsCount;
  } else {
    if (typeof existing === 'function') {
      existing = events[type] =
        prepend ? [listener, existing] : [existing, listener];
    } else if (prepend) {
      existing.unshift(listener);
    } else {
      existing.push(listener);
    }

    // 一部分检查监听器数量的代码,超过就给你报错
  }
  // 优雅的链式调用。。。
  return target;
}

once的实现,也十分简单,不赘述了,就是简单的把上面_addListener包裹下 这种高阶函数的写法倒是比某些人重写一个要好得多

function onceWrapper(...args) {
  if (!this.fired) {
    this.target.removeListener(this.type, this.wrapFn);
    this.fired = true;
    return Reflect.apply(this.listener, this.target, args);
  }
}

function _onceWrap(target, type, listener) {
  const state = { fired: false, wrapFn: undefined, target, type, listener };
  const wrapped = onceWrapper.bind(state);
  wrapped.listener = listener;
  state.wrapFn = wrapped;
  return wrapped;
}

event还提供了一些获取监听函数的,事件名称,设置最大监听数的函数。就不赘述了。

最后居然还提供了一个once函数用来触发某个事件一次 ,可以说是很良心了

浏览器 Event

在浏览器端使用事件机制我们往往会找个库或者自己写一个事件机制。其实浏览器原生就有。(兼容性自己测试哈,只做科普向)

文档看这个

const eventBus = document.createElement('div')
const customEvent = new CustomEvent('demo',{
    test:'test'
})
eventBus.addEventListener('demo',function(e){
	console.log(e)
})

eventBus.dispatchEvent(customEvent);

完结撒花

为啥没有图片?因为用有道云写的。上传要高贵的会员,作图还麻烦。分享只是举手之劳。

超级排序算法压轴

function sortArr(arr){
	arr.forEach(el=>setTimeout(_=>console.log(el),el))
}