title: 微型库解读之200byte的EventEmitter - Mitt
tags: 前端前端
关于 EventEmitter
我想应该不少同窗都很熟悉了。简而言之是一个事件的发布与订阅器。
这两天读到了一些很是有意思的小库,虽然小可是功能完备,好比说此次咱们要讲解的 Mitt.react
Github地址git
Mitt
是一个微型的 EventEmitter
库,实现了基本的 on
, off
, emit
三个Api,对于使用 EventEmitter 其余功能很少的同窗来讲,200byte 的体积能够说是很是划算了。github
固然小也有其付出的代价,那就是只支持这三个功能。
至于怎么取舍,见仁见智吧,我建议是先使用 mitt
,就算后期要更换别的库,由于 Api 统一,因此更换起来基本不费事。函数
Mitt
在 Github的 demo 中,也显示出了代码虽小,五脏俱全的特色。spa
Demo:code
import mitt from 'mitt' let emitter = mitt() // listen to an event emitter.on('foo', e => console.log('foo', e) ) // listen to all events emitter.on('*', (type, e) => console.log(type, e) ) // fire an event emitter.emit('foo', { a: 'b' }) // working with handler references: function onFoo() {} emitter.on('foo', onFoo) // listen emitter.off('foo', onFoo) // unlisten
在研究 Mitt
能完成的功能后,也在想为何能作到这么小。
在这儿对一些闪光点作一些解读。对象
export default function mitt(all: EventHandlerMap) { all = all || Object.create(null); return { // ...Api } }
在初始化 mitt 时,会有一个可选的参数 all
,用于存放要监听的事件。
若是初始化不传参时,会使用 Object.create(null)
来实现。
这样的好处在于,生成的对象是一个原型为空的对象。blog
优势以下:队列
节约内存是由于没有了原型,能够节省部分开销。
避免冲突则是由于在普通对象中,当要触发的事件与对象原型上的属性或方法重名时,会出现事件不存在却被错误触发致使没必要要的问题。
var obj = {}; console.log('toString' in obj); var noPrototypeObj = Object.create(null); console.log('toString' in noPrototypeObj);
输出结果以下:
常常咱们会作这样一个操做,当对象中某个属性不存在时,就初始化,存在则直接返回值。用代码表示就是:
var obj = {}; var getQueue = (key) => { if (!obj[key]) { obj[key] = [] } return obj[key] }
这是一个很常见的操做,可是在 mitt
的却简洁了不少。
export default function mitt(all: EventHandlerMap) { all = all || Object.create(null); return { /** * Register an event handler for the given type. * * @param {String} type Type of event to listen for, or `"*"` for all events * @param {Function} handler Function to call in response to given event * @memberOf mitt */ on(type: string, handler: EventHandler) { (all[type] || (all[type] = [])).push(handler); } }; }
在 on
函数之中,有这么一句:(all[type] || (all[type] = []))
这个表达式的意思很简单,有值取值,无值初始化。
可是总的代码量比起以前的代码小了不少,实现了简化代码的目的。
PS:这个操做我以前在读 React setState 源代码时,也碰到过。
其中 queue 的获取即是使用了这种方式。
在 off
的Api中,有使用到无符号右移(>>>)的操做,具体操做以下:
/** * Remove an event handler for the given type. * * @param {String} type Type of event to unregister `handler` from, or `"*"` * @param {Function} handler Handler function to remove * @memberOf mitt */ off(type: string, handler: EventHandler) { if (all[type]) { all[type].splice(all[type].indexOf(handler) >>> 0, 1); } }
其中 all[type].splice(all[type].indexOf(handler) >>> 0, 1);
这一句的做用可谓亮眼。
移除某个指定的事件监听是很正常的事,但可能会有一个问题,就是传入的要移除的监听器并不存在。
换作以往的代码,可能你会先搜索,再决定是否执行移除操做,可是这样一来代码量就又增长了。
而无符号右移(>>>),偏偏符合咱们的须要。
具体的做用如 demo,在搜索的事件监听函数不存在时,会返回一个极大的正数,传入 splice 后,并不会删除已有的函数监听器,从而实现了想要的功能。
在平常开发中,常常可能想监听全部的事件,来辅助开发。
而 mitt
就实现了这个功能。
Demo:
import mitt from 'mitt' let emitter = mitt() // listen to all events emitter.on('*', (type, e) => console.log(type, e) )
而在源代码里,这个的实现很简洁:
/** * Invoke all handlers for the given type. * If present, `"*"` handlers are invoked after type-matched handlers. * * @param {String} type The event type to invoke * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler * @memberOf mitt */ emit(type: string, evt: any) { (all[type] || []).slice().map((handler) => { handler(evt); }); (all['*'] || []).slice().map((handler) => { handler(type, evt); }); }
就是在使用 emit
函数时,找出事件类型为 *
的监听器,并触发它。
Mitt
的整个库很是的小,可是却功能齐全,为了缩减代码,也是有一些小技巧在里面。
可是 Mitt
的库小也有缺点,好比参数的类型若是传错了,它并不会预先提示你,这也算是一个要取舍的点吧。
这几天也在疯狂的看 developit 写的一些库,他的库都有小而美的特色,不管是著名的 Preact
仍是简单的 mitt
这种库。他写的代码,仍是挺值得一读的。
以后的计划,可能也准备写几篇这种微型库的源码阅读文章,这种库读起来轻松,适合天天读一两个,而能学到的东西和思路也很多。