【若川视野 x 源码共读】第8期 | tiny-emitter

476 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

一个小而巧的发布订阅库,用on去监听事件,用emit去触发事件执行,就像window.addEventLisener

介绍

事件的发布订阅

github1s.com/scottcorgan…

var emitter = require('tiny-emitter/instance');

emitter.on('some-event', function (arg1, arg2, arg3) {
 //
});

emitter.emit('some-event', 'arg1 value', 'arg2 value', 'arg3 value');

on(event, callback[, context])

监听事件

Subscribe to an event

  • event - the name of the event to subscribe to
  • callback - the function to call when event is emitted
  • context - (OPTIONAL) - the context to bind the event callback to

once(event, callback[, context])

只监听一次的事件

Subscribe to an event only once

  • event - the name of the event to subscribe to
  • callback - the function to call when event is emitted
  • context - (OPTIONAL) - the context to bind the event callback to

off(event[, callback])

关闭某事件的监听

Unsubscribe from an event or all events. If no callback is provided, it unsubscribes you from all events.

  • event - the name of the event to unsubscribe from
  • callback - the function used when binding to the event

emit(event[, arguments...])

触发事件

Trigger a named event

  • event - the event name to emit
  • arguments... - any number of arguments to pass to the event subscribers

场景

和vuex类型,可以用来跨组件传递事件和参数

参考

tiny-emitter

github1s.com/scottcorgan…

//一个构造函数
function E () {
  // Keep this empty so it's easier to inherit from
  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
   // this.e={'some-event':[{fn:function(){},ctx:undefinded}]}
    //
}

E.prototype = {
    //事件监听,
  on: function (name, callback, ctx) {
    var e = this.e || (this.e = {});
	//在构造函数里增加变量e,存储函数名和相应事件
    (e[name] || (e[name] = [])).push({
      fn: callback,
      ctx: ctx
    });

    return this;
  },

  once: function (name, callback, ctx) {
    var self = this;
      //定义一个函数listener,包含去除监听和执行一次回调
    function listener () {
        //把变量e里name对应的监听事件listener去掉
      self.off(name, listener);
        //执行一次这个监听函数
      callback.apply(ctx, arguments);
    };
	// 加这个似乎是为了加个标志,在off去掉监听时,不让被off去除
    listener._ = callback
      //给name绑定上这个listener, 触发时里面会去掉这个listener,并且执行一次listener里的监听函数
    return this.on(name, listener, ctx);
  },

  emit: function (name) {
      //去掉事件名,取出参数数组
    var data = [].slice.call(arguments, 1);
      //取出这个事件名对应的监听函数数组
    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
    var i = 0;
    var len = evtArr.length;
	//遍历执行里面的函数
    for (i; i < len; i++) {
      evtArr[i].fn.apply(evtArr[i].ctx, data);
    }

    return this;
  },
	//传入要去掉监听的函数,name里的对应函数
  off: function (name, callback) {
    var e = this.e || (this.e = {});
    var evts = e[name];
    var liveEvents = [];
      //这里我在手写的时候一般用的splice,这里用的其他的方法
	// 这里收集name里函数数组里非去除监听的函数
    if (evts && callback) {
      for (var i = 0, len = evts.length; i < len; i++) {
        if (evts[i].fn !== callback && evts[i].fn._ !== callback)
          liveEvents.push(evts[i]);
      }
    }
	
    // Remove event from queue to prevent memory leak
    // Suggested by https://github.com/lazd
    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
	// 如果去除监听函数后数组空了就干脆删掉这个name
    (liveEvents.length)
      ? e[name] = liveEvents
      : delete e[name];

    return this;
  }
};

module.exports = E;
module.exports.TinyEmitter = E;

为什么他不用splice去做off

  • 之前的off函数用的是splice
		var e =this.e||{}
        var evts = e[name]	  
   for(var i=0,len=evts.length;i<len;i++){
            if(evts[i] &&evts[i].fn !== callback) continue
            evts.splice(i,1)
   }

这里有个问题,看例子 github.com/scottcorgan…

    emitter.on('test', fn);
    emitter.on('test', fn);
    emitter.on('test', fn2);
    emitter.off('test', fn);
    emitter.emit('test');

这里虽然用off去掉了fn,但是仍然会执行一次fn,这就是在for里用splice的弊端,删除数组一项,监听函数数组里的函数index也都挪一位,导致evts遍历的时候漏了一位

你用了splice,那就要判断evts[i]是否为真,而且监听函数数组里的函数index也都挪一位,当然也是可以补救的,我这应该没补错

//删多少补多少
		var e =this.e||{}
        var evts = e[name]	  
   for(var i=0,len=evts.length;i<len;i++){
            if(evts[i] &&evts[i].fn !== callback) continue
            evts.splice(i,1)
            i--
   }

独家特别版

class Observe{
    constructor(){
        this.map = new Map()
    }
    on(name,fn){
        //以后还是写成this.map[name]吧
        if(!this.map.name){this.map.name=[]}
        this.map.name.push(fn)
        return ()=>{
            this.map.name=this.map.name.filter(i=>i!==fn)
            //this.map.name.splice(this.map.name.indexOf(fn),1)
        }
    };
    emit(name,data){
        if(this.map.name){
            this.map.name.forEach(i=>i(data))
        }
    };
    off(name,fn){
        if(this.map.name){
            this.map.name = this.map.name.filter(i=>i!==fn)
        }
    }
    once(name,fn){
        let listener = ()=>{
            this.off(name,listener)
            fn.apply(this,arguments)
        }
        this.on(name,listener)
    }
}
let emitter = new Observe()
let xx = function(data){
    console.log('xx',data)
}
// emitter.on('ss',xx)
// emitter.off('ss',xx) 
//这里学习下redux-reducer
let off = emitter.on('ss',xx)
off()
emitter.emit('ss','xx')