摘要:屬性該方法用于存儲(chǔ)的整個(gè)事件名稱(chēng)與回調(diào)函數(shù)的集合,初始值為。當(dāng)該屬性當(dāng)前值為一個(gè)對(duì)象且其函數(shù)不等于函數(shù)時(shí),則會(huì)將其轉(zhuǎn)換為一個(gè)包含這兩個(gè)事件對(duì)象的事件對(duì)象數(shù)組。
背景
事件監(jiān)聽(tīng)在前端的開(kāi)發(fā)過(guò)程中是一個(gè)很常見(jiàn)的情況。DOM上的事件監(jiān)聽(tīng)方式,讓我們看到了通過(guò)事件的方式來(lái)進(jìn)行具體的業(yè)務(wù)邏輯的處理的便捷。
在具體的一些業(yè)務(wù)場(chǎng)景中,第三方的自定義事件能夠在層級(jí)較多,函數(shù)調(diào)用困難以及需要多個(gè)地方響應(yīng)的時(shí)候有著其獨(dú)特的優(yōu)勢(shì)——調(diào)用方便,避免多層嵌套,降低組件間耦合性。
這篇文章所提到的EventEmitter3,就是一個(gè)典型的第三方事件庫(kù),能夠讓我們通過(guò)自定義的實(shí)踐來(lái)實(shí)現(xiàn)多個(gè)函數(shù)與組件間的通信。
整體結(jié)構(gòu)圖EventEmitter3的設(shè)計(jì)較為的簡(jiǎn)單,具體結(jié)構(gòu)可以看下圖所示。
下面我們將按照一般人的正常思路來(lái)對(duì)這個(gè)結(jié)構(gòu)進(jìn)行介紹。
各部分結(jié)構(gòu)與功能 EEfunction EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; }
從類(lèi)EE的代碼中我們能夠很明確的了解到,第一個(gè)參數(shù)為回調(diào)函數(shù),第二個(gè)參數(shù)為回調(diào)函數(shù)的上下文,第三個(gè)參數(shù)是一個(gè)once的標(biāo)志位。由于代碼簡(jiǎn)單,在這里就簡(jiǎn)單介紹下了。
Prototype屬性 events該方法用于存儲(chǔ)eventEmitter的整個(gè)事件名稱(chēng)與回調(diào)函數(shù)的集合,初始值為undefined。
Prototype方法 eventName作用:返回當(dāng)前已經(jīng)注冊(cè)的事件名稱(chēng)的列表
參數(shù):無(wú)
listeners作用:返回某一個(gè)事件名稱(chēng)的所有監(jiān)聽(tīng)函數(shù)
參數(shù):event——事件名稱(chēng),exists——是否只判斷存在與否
emit作用:觸發(fā)某個(gè)事件
參數(shù):event——事件名,a1~a5——參數(shù)1~5
on作用:為某個(gè)事件添加一個(gè)監(jiān)聽(tīng)函數(shù)
參數(shù):event——事件名,fn——回調(diào)函數(shù),context——上下文
once作用:類(lèi)似on,區(qū)別在于該函數(shù)只會(huì)觸發(fā)一次
參數(shù):event——事件名,fn——回調(diào)函數(shù),context——上下文
removeListner作用:移除某個(gè)事件的監(jiān)聽(tīng)函數(shù)
參數(shù):event——事件名,fn——事件監(jiān)聽(tīng)函數(shù),context——只移除上下文匹配的事件監(jiān)聽(tīng)函數(shù),once——只移除類(lèi)型匹配的事件監(jiān)聽(tīng)函數(shù)
removeAllListener作用:移除某個(gè)時(shí)間的所有監(jiān)聽(tīng)函數(shù)
參數(shù):event——事件名
學(xué)習(xí)思路下面我們將從添加監(jiān)聽(tīng)函數(shù), 事件觸發(fā)與刪除監(jiān)聽(tīng)函數(shù)來(lái)進(jìn)行具體的代碼分析,從而了解該庫(kù)的實(shí)現(xiàn)思路。
事件對(duì)象具體代碼如下所示:
//一個(gè)單一的事件監(jiān)聽(tīng)函數(shù)的單元 // // @param {Function} fn Event handler to be called. 回調(diào)函數(shù) // @param {Mixed} context Context for function execution. 函數(shù)執(zhí)行上下文 // @param {Boolean} [once=false] Only emit once 是否執(zhí)行一次的標(biāo)志位 // @api private 私有API function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; }
該類(lèi)為eventEmitter中用于存儲(chǔ)事件監(jiān)聽(tīng)函數(shù)的最小類(lèi)。
添加監(jiān)聽(tīng)函數(shù)on函數(shù)具體代碼如下所示:
// Register a new EventListener for the given event. // 注冊(cè)一個(gè)指定的事件的事件監(jiān)聽(tīng)函數(shù) // // @param {String} event Name of the event. 事件名 // @param {Function} fn Callback function. 回調(diào)函數(shù) // @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;//第一次存儲(chǔ)為一個(gè)事件監(jiān)聽(tīng)對(duì)象 } else { if (!this._events[evt].fn) {//第三次及以后則直接向?qū)ο髷?shù)組中添加事件監(jiān)聽(tīng)對(duì)象 this._events[evt].push(listener); } else {//第二次將存儲(chǔ)的對(duì)象與新對(duì)象轉(zhuǎn)換為事件監(jiān)聽(tīng)對(duì)象數(shù)組 this._events[evt] = [ this._events[evt], listener ]; } } return this; }
當(dāng)我們向事件E添加函數(shù)F時(shí),會(huì)調(diào)用on方法,此時(shí)on方法會(huì)檢查eventEmitter中prototype屬性events的E屬性。
當(dāng)這個(gè)屬性為undefined時(shí),直接將該函數(shù)所在的事件對(duì)象賦值給evt屬性。
當(dāng)該屬性當(dāng)前值為一個(gè)對(duì)象且其函數(shù)fn不等于函數(shù)F時(shí),則會(huì)將其轉(zhuǎn)換為一個(gè)包含這兩個(gè)事件對(duì)象的事件對(duì)象數(shù)組。
當(dāng)這個(gè)屬性已經(jīng)是一個(gè)對(duì)象數(shù)組時(shí),則直接通過(guò)push方法向數(shù)組中添加對(duì)象。
prefix是用來(lái)判斷Object.create()方法是否存在,如果存在則直接調(diào)用該方法來(lái)創(chuàng)建屬性,否則通過(guò)在屬性前添加~來(lái)避免覆蓋原有屬性。
once的函數(shù)實(shí)現(xiàn)與on函數(shù)基本一致,所以在此就不再進(jìn)行分析。
觸發(fā)監(jiān)聽(tīng)函數(shù)emit函數(shù)代碼如下所示:
// Emit an event to all registered event listeners. // 觸發(fā)已經(jīng)注冊(cè)的事件監(jiān)聽(tīng)函數(shù) // // @param {String} event The name of the event. 事件名 // @returns {Boolean} Indication if we"ve emitted an event. 如果觸發(fā)事件成功,則返回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屬性為數(shù)組時(shí)通過(guò)循環(huán)調(diào)用來(lái)實(shí)現(xiàn)事件觸發(fā)的過(guò)程 } return true; };
當(dāng)我們觸發(fā)事件E時(shí),我們只需要調(diào)用emit方法。該方法會(huì)自動(dòng)檢索事件E中所有的事件監(jiān)聽(tīng)對(duì)象,觸發(fā)所有的事件監(jiān)聽(tīng)函數(shù),同時(shí)移除掉通過(guò)once添加,只需要觸發(fā)一次的事件監(jiān)聽(tīng)函數(shù)。
移除事件監(jiān)聽(tīng)函數(shù)removeListener函數(shù)代碼如下:
// Remove event listeners. // 移除事件監(jiān)聽(tīng)函數(shù) // // @param {String} event The event we want to remove. 需要被移除的事件名 // @param {Function} fn The listener that we need to find. 需要被移除的事件監(jiān)聽(tīng)函數(shù) // @param {Mixed} context Only remove listeners matching this context. 只移除匹配該參數(shù)指定的上下文的監(jiān)聽(tīng)函數(shù) // @param {Boolean} once Only remove once listeners. 只移除匹配該參數(shù)指定的once屬性的監(jiān)聽(tīng)函數(shù) // @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屬性查找函數(shù)刪除的代碼 } } // // 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函數(shù)實(shí)現(xiàn)較為簡(jiǎn)單。當(dāng)我們需要移除事件E的某個(gè)函數(shù)時(shí),它使用一個(gè)event屬性來(lái)保存不需要被移除的事件監(jiān)聽(tīng)對(duì)象,然后便利整個(gè)事件監(jiān)聽(tīng)數(shù)組(單個(gè)時(shí)為對(duì)象),并且最后將event屬性的值賦值給E屬性從而覆蓋掉原有的屬性,達(dá)到刪除的目的。
其他該庫(kù)中還有一些其他的函數(shù),由于對(duì)整個(gè)庫(kù)的理解不產(chǎn)生太大影響,因此沒(méi)有在此進(jìn)行講解,有需要的可以前往我的github倉(cāng)庫(kù)進(jìn)行查看。
缺點(diǎn)eventEmitter的代碼雖然結(jié)構(gòu)清晰,但是仍然存在一些問(wèn)題。例如on和once方法的實(shí)現(xiàn)中,只有一個(gè)屬性不同,其余代碼都一模一樣,其實(shí)可以抽出一個(gè)特定的函數(shù)來(lái)進(jìn)行處理,通過(guò)屬性來(lái)進(jìn)行區(qū)分調(diào)用即可。
同時(shí),在同一個(gè)函數(shù)例如emit中,也存在大量的重復(fù)代碼,可以進(jìn)行進(jìn)一步的抽象和整理,使得代碼更加簡(jiǎn)單。
總結(jié)eventEmitter第三方事件庫(kù)從實(shí)現(xiàn)上來(lái)看較為簡(jiǎn)單,并且結(jié)構(gòu)清晰容易閱讀,推薦有興趣的可以花大約一個(gè)小時(shí)的時(shí)間來(lái)學(xué)習(xí)下。
附錄本人github地址——eventEmitter3
官方github地址
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/87759.html
摘要:事件刪除可有可無(wú)。創(chuàng)建了一個(gè)類(lèi),然后在構(gòu)造函數(shù)里初始化一個(gè)類(lèi)的屬性,這個(gè)屬性不需要要繼承任何東西,所以用了。但這不是必要的,因?yàn)閷?shí)例化一個(gè)都會(huì)調(diào)用構(gòu)造函數(shù),皆為初始狀態(tài),應(yīng)該是不可能已經(jīng)定義了的,可去掉。成功執(zhí)行結(jié)束后返回。 GitHub地址:JavaScript EventEmitter博客地址:JavaScript EventEmitter 水平有限,歡迎批評(píng)指正 2個(gè)多月前把 ...
摘要:批處理的程序分析博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡(jiǎn)單程序入門(mén)從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)轉(zhuǎn)換從到學(xué)習(xí)介紹中的從到學(xué)習(xí)中的幾種詳解從到學(xué)習(xí)讀取數(shù)據(jù)寫(xiě)入到從到學(xué)習(xí)項(xiàng) 批處理的 WordCount 程序分析: https://t.zsxq.com/YJ2Zrfi 博客 1、Flink 從0到1學(xué)習(xí) —— Apache ...
摘要:再如通過(guò)處理流數(shù)據(jù)生成簡(jiǎn)單的報(bào)告,如五分鐘的窗口聚合數(shù)據(jù)平均值。復(fù)雜的事情還有在流數(shù)據(jù)中進(jìn)行數(shù)據(jù)多維度關(guān)聯(lián)聚合塞選,從而找到復(fù)雜事件中的根因。因?yàn)楦鞣N需求,也就造就了現(xiàn)在不斷出現(xiàn)實(shí)時(shí)計(jì)算框架,而下文我們將重磅介紹我們推薦的實(shí)時(shí)計(jì)算框架。 前言 先廣而告之,本文摘自本人《大數(shù)據(jù)重磅炸彈——實(shí)時(shí)計(jì)算框架 Flink》課程第二篇,內(nèi)容首發(fā)自我的知識(shí)星球,后面持續(xù)在星球里更新,這里做個(gè)預(yù)告,今...
摘要:背景在工作中雖然我經(jīng)常使用到庫(kù)但是很多時(shí)候?qū)Φ囊恍└拍钸€是處于知其然不知其所以然的狀態(tài)因此就萌生了學(xué)習(xí)源碼的想法剛開(kāi)始看源碼的時(shí)候自然是比較痛苦的主要原因有兩個(gè)第一網(wǎng)上沒(méi)有找到讓我滿意的詳盡的源碼分析的教程第二我也是第一次系統(tǒng)地學(xué)習(xí)這么大代 背景 在工作中, 雖然我經(jīng)常使用到 Netty 庫(kù), 但是很多時(shí)候?qū)?Netty 的一些概念還是處于知其然, 不知其所以然的狀態(tài), 因此就萌生了學(xué)...
摘要:引言給迷失在如何學(xué)習(xí)區(qū)塊鏈技術(shù)的同學(xué)一個(gè)指引,區(qū)塊鏈技術(shù)是隨比特幣誕生,因此要搞明白區(qū)塊鏈技術(shù),應(yīng)該先了解下比特幣。但區(qū)塊鏈技術(shù)不單應(yīng)用于比特幣,還有非常多的現(xiàn)實(shí)應(yīng)用場(chǎng)景,想做區(qū)塊鏈應(yīng)用開(kāi)發(fā),可進(jìn)一步閱讀以太坊系列。 本文始發(fā)于深入淺出區(qū)塊鏈社區(qū), 原文:區(qū)塊鏈技術(shù)學(xué)習(xí)指引 原文已更新,請(qǐng)讀者前往原文閱讀 本章的文章越來(lái)越多,本文是一個(gè)索引帖,方便找到自己感興趣的文章,你也可以使用左側(cè)...
閱讀 2400·2021-11-24 10:33
閱讀 1471·2019-08-30 15:43
閱讀 3361·2019-08-29 17:24
閱讀 3566·2019-08-29 14:21
閱讀 2299·2019-08-29 13:59
閱讀 1824·2019-08-29 11:12
閱讀 2920·2019-08-28 18:00
閱讀 1953·2019-08-26 12:17