摘要:屬性該方法用于存儲的整個事件名稱與回調函數的集合,初始值為。當該屬性當前值為一個對象且其函數不等于函數時,則會將其轉換為一個包含這兩個事件對象的事件對象數組。
背景
事件監聽在前端的開發過程中是一個很常見的情況。DOM上的事件監聽方式,讓我們看到了通過事件的方式來進行具體的業務邏輯的處理的便捷。
在具體的一些業務場景中,第三方的自定義事件能夠在層級較多,函數調用困難以及需要多個地方響應的時候有著其獨特的優勢——調用方便,避免多層嵌套,降低組件間耦合性。
這篇文章所提到的EventEmitter3,就是一個典型的第三方事件庫,能夠讓我們通過自定義的實踐來實現多個函數與組件間的通信。
整體結構圖EventEmitter3的設計較為的簡單,具體結構可以看下圖所示。
下面我們將按照一般人的正常思路來對這個結構進行介紹。
各部分結構與功能 EEfunction EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; }
從類EE的代碼中我們能夠很明確的了解到,第一個參數為回調函數,第二個參數為回調函數的上下文,第三個參數是一個once的標志位。由于代碼簡單,在這里就簡單介紹下了。
Prototype屬性 events該方法用于存儲eventEmitter的整個事件名稱與回調函數的集合,初始值為undefined。
Prototype方法 eventName作用:返回當前已經注冊的事件名稱的列表
參數:無
listeners作用:返回某一個事件名稱的所有監聽函數
參數:event——事件名稱,exists——是否只判斷存在與否
emit作用:觸發某個事件
參數:event——事件名,a1~a5——參數1~5
on作用:為某個事件添加一個監聽函數
參數:event——事件名,fn——回調函數,context——上下文
once作用:類似on,區別在于該函數只會觸發一次
參數:event——事件名,fn——回調函數,context——上下文
removeListner作用:移除某個事件的監聽函數
參數:event——事件名,fn——事件監聽函數,context——只移除上下文匹配的事件監聽函數,once——只移除類型匹配的事件監聽函數
removeAllListener作用:移除某個時間的所有監聽函數
參數:event——事件名
學習思路下面我們將從添加監聽函數, 事件觸發與刪除監聽函數來進行具體的代碼分析,從而了解該庫的實現思路。
事件對象具體代碼如下所示:
//一個單一的事件監聽函數的單元 // // @param {Function} fn Event handler to be called. 回調函數 // @param {Mixed} context Context for function execution. 函數執行上下文 // @param {Boolean} [once=false] Only emit once 是否執行一次的標志位 // @api private 私有API function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; }
該類為eventEmitter中用于存儲事件監聽函數的最小類。
添加監聽函數on函數具體代碼如下所示:
// Register a new EventListener for the given event. // 注冊一個指定的事件的事件監聽函數 // // @param {String} event Name of the event. 事件名 // @param {Function} fn Callback function. 回調函數 // @param {Mixed} [context=this] The context of the function. 上下文 // @api public 公有API EventEmitter.prototype.on = function on(event, fn, context) { var listener = new EE(fn, context || this) , evt = prefix ? prefix + event : event; if (!this._events) this._events = prefix ? {} : Object.create(null); if (!this._events[evt]) { this._events[evt] = listener;//第一次存儲為一個事件監聽對象 } else { if (!this._events[evt].fn) {//第三次及以后則直接向對象數組中添加事件監聽對象 this._events[evt].push(listener); } else {//第二次將存儲的對象與新對象轉換為事件監聽對象數組 this._events[evt] = [ this._events[evt], listener ]; } } return this; }
當我們向事件E添加函數F時,會調用on方法,此時on方法會檢查eventEmitter中prototype屬性events的E屬性。
當這個屬性為undefined時,直接將該函數所在的事件對象賦值給evt屬性。
當該屬性當前值為一個對象且其函數fn不等于函數F時,則會將其轉換為一個包含這兩個事件對象的事件對象數組。
當這個屬性已經是一個對象數組時,則直接通過push方法向數組中添加對象。
prefix是用來判斷Object.create()方法是否存在,如果存在則直接調用該方法來創建屬性,否則通過在屬性前添加~來避免覆蓋原有屬性。
once的函數實現與on函數基本一致,所以在此就不再進行分析。
觸發監聽函數emit函數代碼如下所示:
// Emit an event to all registered event listeners. // 觸發已經注冊的事件監聽函數 // // @param {String} event The name of the event. 事件名 // @returns {Boolean} Indication if we"ve emitted an event. 如果觸發事件成功,則返回true,否則返回false // @api public 公有API EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if ("function" === typeof listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch(len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for(i = 1, args = new Array(len - 1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { //由于篇幅原因省略E屬性為數組時通過循環調用來實現事件觸發的過程 } return true; };
當我們觸發事件E時,我們只需要調用emit方法。該方法會自動檢索事件E中所有的事件監聽對象,觸發所有的事件監聽函數,同時移除掉通過once添加,只需要觸發一次的事件監聽函數。
移除事件監聽函數removeListener函數代碼如下:
// Remove event listeners. // 移除事件監聽函數 // // @param {String} event The event we want to remove. 需要被移除的事件名 // @param {Function} fn The listener that we need to find. 需要被移除的事件監聽函數 // @param {Mixed} context Only remove listeners matching this context. 只移除匹配該參數指定的上下文的監聽函數 // @param {Boolean} once Only remove once listeners. 只移除匹配該參數指定的once屬性的監聽函數 // @api public 公共API EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return this; var listeners = this._events[evt] , events = []; if (fn) { if (listeners.fn) { if ( listeners.fn !== fn || (once && !listeners.once) || (context && listeners.context !== context) ) { events.push(listeners); } } else { //由于篇幅原因省去便利listeners屬性查找函數刪除的代碼 } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) { this._events[evt] = events.length === 1 ? events[0] : events; } else { delete this._events[evt]; } return this; };
removeListener函數實現較為簡單。當我們需要移除事件E的某個函數時,它使用一個event屬性來保存不需要被移除的事件監聽對象,然后便利整個事件監聽數組(單個時為對象),并且最后將event屬性的值賦值給E屬性從而覆蓋掉原有的屬性,達到刪除的目的。
其他該庫中還有一些其他的函數,由于對整個庫的理解不產生太大影響,因此沒有在此進行講解,有需要的可以前往我的github倉庫進行查看。
缺點eventEmitter的代碼雖然結構清晰,但是仍然存在一些問題。例如on和once方法的實現中,只有一個屬性不同,其余代碼都一模一樣,其實可以抽出一個特定的函數來進行處理,通過屬性來進行區分調用即可。
同時,在同一個函數例如emit中,也存在大量的重復代碼,可以進行進一步的抽象和整理,使得代碼更加簡單。
總結eventEmitter第三方事件庫從實現上來看較為簡單,并且結構清晰容易閱讀,推薦有興趣的可以花大約一個小時的時間來學習下。
附錄本人github地址——eventEmitter3
官方github地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87759.html
摘要:事件刪除可有可無。創建了一個類,然后在構造函數里初始化一個類的屬性,這個屬性不需要要繼承任何東西,所以用了。但這不是必要的,因為實例化一個都會調用構造函數,皆為初始狀態,應該是不可能已經定義了的,可去掉。成功執行結束后返回。 GitHub地址:JavaScript EventEmitter博客地址:JavaScript EventEmitter 水平有限,歡迎批評指正 2個多月前把 ...
摘要:批處理的程序分析博客從到學習介紹從到學習上搭建環境并構建運行簡單程序入門從到學習配置文件詳解從到學習介紹從到學習如何自定義從到學習介紹從到學習如何自定義從到學習轉換從到學習介紹中的從到學習中的幾種詳解從到學習讀取數據寫入到從到學習項 批處理的 WordCount 程序分析: https://t.zsxq.com/YJ2Zrfi 博客 1、Flink 從0到1學習 —— Apache ...
摘要:再如通過處理流數據生成簡單的報告,如五分鐘的窗口聚合數據平均值。復雜的事情還有在流數據中進行數據多維度關聯聚合塞選,從而找到復雜事件中的根因。因為各種需求,也就造就了現在不斷出現實時計算框架,而下文我們將重磅介紹我們推薦的實時計算框架。 前言 先廣而告之,本文摘自本人《大數據重磅炸彈——實時計算框架 Flink》課程第二篇,內容首發自我的知識星球,后面持續在星球里更新,這里做個預告,今...
摘要:背景在工作中雖然我經常使用到庫但是很多時候對的一些概念還是處于知其然不知其所以然的狀態因此就萌生了學習源碼的想法剛開始看源碼的時候自然是比較痛苦的主要原因有兩個第一網上沒有找到讓我滿意的詳盡的源碼分析的教程第二我也是第一次系統地學習這么大代 背景 在工作中, 雖然我經常使用到 Netty 庫, 但是很多時候對 Netty 的一些概念還是處于知其然, 不知其所以然的狀態, 因此就萌生了學...
閱讀 2315·2021-11-24 10:33
閱讀 1385·2019-08-30 15:43
閱讀 3276·2019-08-29 17:24
閱讀 3481·2019-08-29 14:21
閱讀 2220·2019-08-29 13:59
閱讀 1735·2019-08-29 11:12
閱讀 2811·2019-08-28 18:00
閱讀 1849·2019-08-26 12:17