摘要:用于在同一主模塊下的不同子模塊以及不同主模塊之間的通信,支持動(dòng)態(tài)綁定作用域。如果用過的父子組件事件通信以及,對(duì)事件管理器應(yīng)該不會(huì)陌生的。而且支持指定作用域,可以遠(yuǎn)程調(diào)用任意模塊的函數(shù)。
上一篇文章介紹了clipboard.js這個(gè)工具庫(kù)中的第一個(gè)依賴select這個(gè)工具庫(kù)主要完成了對(duì)任意DOM元素的復(fù)制到粘貼板的功能。這次介紹一下clipboard.js源碼中的第二個(gè)依賴的輕型工具庫(kù)tiny-emitter這個(gè)工具庫(kù)主要用來實(shí)現(xiàn)一個(gè)簡(jiǎn)易的基于監(jiān)聽發(fā)布者模式的事件派發(fā)和接收器,代碼經(jīng)過我的es6改寫后只有40行,沒有依賴第三方庫(kù),實(shí)現(xiàn)的功能卻是比較強(qiáng)大的,而且可以根據(jù)實(shí)際情況方便的進(jìn)行擴(kuò)展。
快速上手在研究源碼之前,先看一下最普遍的使用場(chǎng)景。
const Emitter = require("./emitter") let emitter = new Emitter() // on 一個(gè)事件 let sayHello = name => console.log(`hello, ${name}`) emitter.on("helloName", sayHello) // emit 一個(gè)事件 // emitter.emit("helloName", "dongzhe") // on一個(gè)帶有作用域的同一個(gè)事件 let obj = { prefix: "smith", thankName (name) { console.log(`hello, ${this.prefix}.${name}`) return `hello, ${this.prefix}.${name}` } } emitter.on("helloName", obj.thankName, obj) emitter.emit("helloName", "dongzhe") // new other emitter 可以在這里分組 不同的組可以有同樣的eventName let emitter1 = new Emitter() let sayHaHa = name => console.log(`haha, ${name}`) emitter1.on("helloName", sayHaHa) // emit 一個(gè)事件 emitter1.emit("helloName", "dongzhe")
可以看出,每一個(gè)事件管理器都是一個(gè)對(duì)象,可以根據(jù)不同的業(yè)務(wù)場(chǎng)景模塊創(chuàng)建不同的事件管理器,事件管理器最基本功能就是動(dòng)態(tài)的訂閱事件和派發(fā)事件,當(dāng)然還可以取消事件。用于在同一主模塊下的不同子模塊以及不同主模塊之間的通信,支持動(dòng)態(tài)綁定作用域。如果用過vue的父子組件事件通信以及eventBus,對(duì)事件管理器應(yīng)該不會(huì)陌生的。
源碼實(shí)現(xiàn)事件管理模型主要由4個(gè)函數(shù)構(gòu)成,
on 用于訂閱事件,一個(gè)事件訂閱多個(gè)觸發(fā)函數(shù)
emit 用于發(fā)布事件,發(fā)布時(shí)會(huì)以此觸發(fā)事件訂閱的函數(shù)
once 訂閱的事件只觸發(fā)一次
off 取消訂閱事件,支持指定取消,批量取消和全部取消
代碼結(jié)構(gòu)
class E { constructor () { this.eventObj = {} } on () {} once () {} emit () {} off () {} } module.exports = E
Emitter對(duì)象存在一個(gè)事件對(duì)象,以鍵值對(duì)的形式保存事件名稱和對(duì)應(yīng)的觸發(fā)事件。
訂閱事件 on訂閱事件就是把要觸發(fā)的函數(shù)放到事件對(duì)應(yīng)的對(duì)象里面,如果事件不存在,需要初始化一下即可。一個(gè)事件可以動(dòng)態(tài)的訂閱多個(gè)觸發(fā)函數(shù)。而且支持指定作用域,可以遠(yuǎn)程調(diào)用任意模塊的函數(shù)。
on (eventName, callback, ctx) { // 一個(gè)eventName可以綁定多個(gè)事件 (this.eventObj[eventName] || (this.eventObj[eventName] = [])).push({callback, ctx}) return this }發(fā)布事件 emit
相對(duì)訂閱事件的就是發(fā)布事件,發(fā)布事件接收事件的事件名和觸發(fā)函數(shù)的參數(shù),將對(duì)應(yīng)事件訂閱的觸發(fā)函數(shù)依次執(zhí)行即可,參數(shù)可以使用es6的rest操作符。
emit (eventName, ...args) { let eventArr = (this.eventObj[eventName] || []).slice() eventArr.forEach(ele => ele.callback.call(ele.ctx, args)) return this }取消事件 off
相對(duì)訂閱事件,也應(yīng)該可以取消事件,取消事件可以有多種選擇,可以指定取消事件訂閱的某一個(gè)或者多個(gè)觸發(fā)函數(shù),也可以直接將整個(gè)事件都取消掉。取消事件接收取消的事件名稱,和一個(gè)可選的函數(shù)對(duì)象或者函數(shù)對(duì)象數(shù)組(我自己增加的),如果傳入了指定的觸發(fā)函數(shù)對(duì)象,通過遍歷所有觸發(fā)的函數(shù)來過濾掉需要取消的觸發(fā)函數(shù),最后重新賦值即可。如果沒有傳觸發(fā)函數(shù),那么就認(rèn)為取消整個(gè)訂閱的事件,直接從全局的事件對(duì)象中刪除訂閱對(duì)象即可
off (eventName, callback) { if (Object.prototype.toString.call(callback) === "[object Array]") { callback.forEach(func => this.off(eventName, func)) return this } let liveEvents = [] let obj = this.eventObj let eventArr = obj[eventName] // 如果沒有callback 就刪除掉整個(gè)eventName對(duì)象 if (eventArr && callback) { liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback)) } (liveEvents.length) ? obj[eventName] = liveEvents : delete obj[eventName] return this }
其中最主要的就是下面這一行代碼了,使用filter過濾掉需要取消的觸發(fā)函數(shù),ele.callback._ !== callback是為了兼容once后面馬上就說到。
liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback))一次觸發(fā) once
有的時(shí)候我們只需要觸發(fā)一次訂閱的事件,比如用戶剛登錄進(jìn)來獲取歷史消息或者通知消息,觸發(fā)一次后就不需要了,所以有了once函數(shù),once函數(shù)主要的工作原理就是,在函數(shù)內(nèi)部添加一個(gè)代理函數(shù)listener代理函數(shù)用來為觸發(fā)函數(shù)做代理,做代理的目的是為了添加邏輯,這個(gè)邏輯就是在觸發(fā)函數(shù)第一次執(zhí)行的時(shí)候,就自動(dòng)執(zhí)行off函數(shù),用來取消觸發(fā)函數(shù)的邏輯。
let listener = (...args) => { this.off(eventName, listener) callback.apply(ctx, args) } // 因?yàn)閘istener是在callback上封裝了一層 所以要規(guī)定一個(gè)可以找到callbak的規(guī)則 listener._ = callback
因?yàn)?b>listener是在callback上封裝了一層代理 所以要規(guī)定一個(gè)可以找到callback的規(guī)則,這樣off函數(shù)在傳入取消函數(shù)的時(shí)候,我們可以順利的用兼容的方式找到。
最后其實(shí)訂閱的是這個(gè)代理函數(shù)listener
once (eventName, callback, ctx) { let listener = (...args) => { this.off(eventName, listener) callback.apply(ctx, args) } // 因?yàn)閘istener是在callback上封裝了一層 所以要規(guī)定一個(gè)可以找到callbak的規(guī)則 listener._ = callback return this.on(eventName, listener, ctx) }完整代碼
我自己在原來的代碼基礎(chǔ)上用es6重新編寫,并添加了一些邏輯,可以對(duì)比原來的代碼來看,最后完整的代碼如下
class E { constructor () { this.eventObj = {} } on (eventName, callback, ctx) { // 一個(gè)eventName可以綁定多個(gè)事件 (this.eventObj[eventName] || (this.eventObj[eventName] = [])).push({callback, ctx}) return this } once (eventName, callback, ctx) { let listener = (...args) => { this.off(eventName, listener) callback.apply(ctx, args) } // 因?yàn)閘istener是在callback上封裝了一層 所以要規(guī)定一個(gè)可以找到callbak的規(guī)則 listener._ = callback return this.on(eventName, listener, ctx) } emit (eventName, ...args) { let eventArr = (this.eventObj[eventName] || []).slice() eventArr.forEach(ele => ele.callback.call(ele.ctx, args)) return this } off (eventName, callback) { if (Object.prototype.toString.call(callback) === "[object Array]") { callback.forEach(func => this.off(eventName, func)) return this } let liveEvents = [] let obj = this.eventObj let eventArr = obj[eventName] // 如果沒有callback 就刪除掉整個(gè)eventName對(duì)象 if (eventArr && callback) { liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback)) } (liveEvents.length) ? obj[eventName] = liveEvents : delete obj[eventName] return this } } module.exports = E結(jié)語(yǔ)
這只是一個(gè)比較簡(jiǎn)單的事件訂閱發(fā)布器,但包含的核心思想還是比較完整的,用到了面向?qū)ο螅嗛啺l(fā)布者模式,代理模式等,而且可以根據(jù)自己的需求進(jìn)行很方便的擴(kuò)展,比如我擴(kuò)展的批量取消,也可以添加批量訂閱,甚至使用promise來封裝異步觸發(fā),每一個(gè)函數(shù)都返回了對(duì)象本身,可以完成鏈?zhǔn)秸{(diào)用,比如訂閱完成后立刻觸發(fā)完成初始化等等。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/93133.html
摘要:上一篇文章介紹了這個(gè)工具庫(kù)中的第二個(gè)依賴,這個(gè)工具庫(kù)主要完成了一個(gè)簡(jiǎn)易的事件訂閱發(fā)布器。節(jié)點(diǎn)事件綁定判斷一個(gè)元素是否是節(jié)點(diǎn),是通過構(gòu)造函數(shù)和屬性來判斷的。 上一篇文章介紹了clipboard.js這個(gè)工具庫(kù)中的第二個(gè)依賴tiny-emitter,這個(gè)工具庫(kù)主要完成了一個(gè)簡(jiǎn)易的事件訂閱發(fā)布器。這次介紹一下clipboard.js源碼中的最后一個(gè)依賴的輕型工具庫(kù)good-listener,...
摘要:與的關(guān)系既是表達(dá)文檔,又表達(dá)文檔修改。然后會(huì)監(jiān)聽事件,然后觸發(fā)的方法,傳入?yún)?shù),然后在的方法中,會(huì)依據(jù)構(gòu)建出對(duì)應(yīng)的數(shù)組,與已有的合并,使當(dāng)前保持最新。 背景分析/技術(shù)選型 quillAPI驅(qū)動(dòng)設(shè)計(jì),自定義內(nèi)容和格式化,跨平臺(tái),易用. CKEditor功能強(qiáng),配置靈活,ui漂亮,兼容性差 TinyMCE文檔好,功能強(qiáng),bug少,無外部依賴。 UEditor功能齊全,但是不維護(hù)了,依賴j...
摘要:下面對(duì)它的實(shí)現(xiàn)一一分析??梢允褂毛@取選中的內(nèi)容也可以使用獲取一個(gè)用戶選擇的范圍。在這里完成了對(duì)用戶選中內(nèi)容的一些操作,而且在不是表單無法觸發(fā)事件的時(shí)候,也可以在指定區(qū)域監(jiān)聽事件來實(shí)時(shí)獲取選中的內(nèi)容完成復(fù)制等功能。 項(xiàng)目中用到了選中復(fù)制功能 showImg(https://segmentfault.com/img/bVY7dH?w=400&h=78); 就是點(diǎn)擊按鈕,復(fù)制左側(cè)的內(nèi)容到剪切...
閱讀 3580·2021-09-22 10:52
閱讀 1597·2021-09-09 09:34
閱讀 1998·2021-09-09 09:33
閱讀 766·2019-08-30 15:54
閱讀 2681·2019-08-29 11:15
閱讀 724·2019-08-26 13:37
閱讀 1677·2019-08-26 12:11
閱讀 2984·2019-08-26 12:00