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
這種庫。他寫的代碼,仍是挺值得一讀的。
以後的計劃,可能也準備寫幾篇這種微型庫的源碼閱讀文章,這種庫讀起來輕鬆,適合天天讀一兩個,而能學到的東西和思路也很多。