摘要:用于在同一主模塊下的不同子模塊以及不同主模塊之間的通信,支持動態綁定作用域。如果用過的父子組件事件通信以及,對事件管理器應該不會陌生的。而且支持指定作用域,可以遠程調用任意模塊的函數。
上一篇文章介紹了clipboard.js這個工具庫中的第一個依賴select這個工具庫主要完成了對任意DOM元素的復制到粘貼板的功能。這次介紹一下clipboard.js源碼中的第二個依賴的輕型工具庫tiny-emitter這個工具庫主要用來實現一個簡易的基于監聽發布者模式的事件派發和接收器,代碼經過我的es6改寫后只有40行,沒有依賴第三方庫,實現的功能卻是比較強大的,而且可以根據實際情況方便的進行擴展。
快速上手在研究源碼之前,先看一下最普遍的使用場景。
const Emitter = require("./emitter") let emitter = new Emitter() // on 一個事件 let sayHello = name => console.log(`hello, ${name}`) emitter.on("helloName", sayHello) // emit 一個事件 // emitter.emit("helloName", "dongzhe") // on一個帶有作用域的同一個事件 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 一個事件 emitter1.emit("helloName", "dongzhe")
可以看出,每一個事件管理器都是一個對象,可以根據不同的業務場景模塊創建不同的事件管理器,事件管理器最基本功能就是動態的訂閱事件和派發事件,當然還可以取消事件。用于在同一主模塊下的不同子模塊以及不同主模塊之間的通信,支持動態綁定作用域。如果用過vue的父子組件事件通信以及eventBus,對事件管理器應該不會陌生的。
源碼實現事件管理模型主要由4個函數構成,
on 用于訂閱事件,一個事件訂閱多個觸發函數
emit 用于發布事件,發布時會以此觸發事件訂閱的函數
once 訂閱的事件只觸發一次
off 取消訂閱事件,支持指定取消,批量取消和全部取消
代碼結構
class E { constructor () { this.eventObj = {} } on () {} once () {} emit () {} off () {} } module.exports = E
Emitter對象存在一個事件對象,以鍵值對的形式保存事件名稱和對應的觸發事件。
訂閱事件 on訂閱事件就是把要觸發的函數放到事件對應的對象里面,如果事件不存在,需要初始化一下即可。一個事件可以動態的訂閱多個觸發函數。而且支持指定作用域,可以遠程調用任意模塊的函數。
on (eventName, callback, ctx) { // 一個eventName可以綁定多個事件 (this.eventObj[eventName] || (this.eventObj[eventName] = [])).push({callback, ctx}) return this }發布事件 emit
相對訂閱事件的就是發布事件,發布事件接收事件的事件名和觸發函數的參數,將對應事件訂閱的觸發函數依次執行即可,參數可以使用es6的rest操作符。
emit (eventName, ...args) { let eventArr = (this.eventObj[eventName] || []).slice() eventArr.forEach(ele => ele.callback.call(ele.ctx, args)) return this }取消事件 off
相對訂閱事件,也應該可以取消事件,取消事件可以有多種選擇,可以指定取消事件訂閱的某一個或者多個觸發函數,也可以直接將整個事件都取消掉。取消事件接收取消的事件名稱,和一個可選的函數對象或者函數對象數組(我自己增加的),如果傳入了指定的觸發函數對象,通過遍歷所有觸發的函數來過濾掉需要取消的觸發函數,最后重新賦值即可。如果沒有傳觸發函數,那么就認為取消整個訂閱的事件,直接從全局的事件對象中刪除訂閱對象即可
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 就刪除掉整個eventName對象 if (eventArr && callback) { liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback)) } (liveEvents.length) ? obj[eventName] = liveEvents : delete obj[eventName] return this }
其中最主要的就是下面這一行代碼了,使用filter過濾掉需要取消的觸發函數,ele.callback._ !== callback是為了兼容once后面馬上就說到。
liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback))一次觸發 once
有的時候我們只需要觸發一次訂閱的事件,比如用戶剛登錄進來獲取歷史消息或者通知消息,觸發一次后就不需要了,所以有了once函數,once函數主要的工作原理就是,在函數內部添加一個代理函數listener代理函數用來為觸發函數做代理,做代理的目的是為了添加邏輯,這個邏輯就是在觸發函數第一次執行的時候,就自動執行off函數,用來取消觸發函數的邏輯。
let listener = (...args) => { this.off(eventName, listener) callback.apply(ctx, args) } // 因為listener是在callback上封裝了一層 所以要規定一個可以找到callbak的規則 listener._ = callback
因為listener是在callback上封裝了一層代理 所以要規定一個可以找到callback的規則,這樣off函數在傳入取消函數的時候,我們可以順利的用兼容的方式找到。
最后其實訂閱的是這個代理函數listener
once (eventName, callback, ctx) { let listener = (...args) => { this.off(eventName, listener) callback.apply(ctx, args) } // 因為listener是在callback上封裝了一層 所以要規定一個可以找到callbak的規則 listener._ = callback return this.on(eventName, listener, ctx) }完整代碼
我自己在原來的代碼基礎上用es6重新編寫,并添加了一些邏輯,可以對比原來的代碼來看,最后完整的代碼如下
class E { constructor () { this.eventObj = {} } on (eventName, callback, ctx) { // 一個eventName可以綁定多個事件 (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) } // 因為listener是在callback上封裝了一層 所以要規定一個可以找到callbak的規則 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 就刪除掉整個eventName對象 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結語
這只是一個比較簡單的事件訂閱發布器,但包含的核心思想還是比較完整的,用到了面向對象,訂閱發布者模式,代理模式等,而且可以根據自己的需求進行很方便的擴展,比如我擴展的批量取消,也可以添加批量訂閱,甚至使用promise來封裝異步觸發,每一個函數都返回了對象本身,可以完成鏈式調用,比如訂閱完成后立刻觸發完成初始化等等。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93133.html
摘要:上一篇文章介紹了這個工具庫中的第二個依賴,這個工具庫主要完成了一個簡易的事件訂閱發布器。節點事件綁定判斷一個元素是否是節點,是通過構造函數和屬性來判斷的。 上一篇文章介紹了clipboard.js這個工具庫中的第二個依賴tiny-emitter,這個工具庫主要完成了一個簡易的事件訂閱發布器。這次介紹一下clipboard.js源碼中的最后一個依賴的輕型工具庫good-listener,...
摘要:下面對它的實現一一分析。可以使用獲取選中的內容也可以使用獲取一個用戶選擇的范圍。在這里完成了對用戶選中內容的一些操作,而且在不是表單無法觸發事件的時候,也可以在指定區域監聽事件來實時獲取選中的內容完成復制等功能。 項目中用到了選中復制功能 showImg(https://segmentfault.com/img/bVY7dH?w=400&h=78); 就是點擊按鈕,復制左側的內容到剪切...
閱讀 3560·2021-09-22 10:52
閱讀 1588·2021-09-09 09:34
閱讀 1990·2021-09-09 09:33
閱讀 758·2019-08-30 15:54
閱讀 2596·2019-08-29 11:15
閱讀 713·2019-08-26 13:37
閱讀 1667·2019-08-26 12:11
閱讀 2975·2019-08-26 12:00