import Taro, { Events } from '@tarojs/taro'
const events = new Events()
// 監聽一個事件,接受參數
events.on('eventName', (arg) => {
// doSth
})
// 監聽同個事件,同時綁定多個 handler
events.on('eventName', handler1)
events.on('eventName', handler2)
events.on('eventName', handler3)
// 觸發一個事件,傳參
events.trigger('eventName', arg)
// 觸發事件,傳入多個參數
events.trigger('eventName', arg1, arg2, ...)
// 取消監聽一個事件
events.off('eventName')
// 取消監聽一個事件某個 handler
events.off('eventName', handler1)
// 取消監聽全部事件
events.off()
複製代碼
大體看下源碼,有一個總體觀,切記一上來就一股腦鑽進某個細節裏。javascript
從源碼裏,能夠大概知道,做者是經過class
面向對象的方式實現,內部管理一個callbacks
對象,註冊一個事件,往對象裏添加一個事件屬性對象,事件屬性對象下有幾個屬性,一個是事件回調函數callback
,一個是執行上下文context
,還有一個next
, 做用是多個callback
對象嵌套。java
觸發事件時,經過事件名,找到對應的對象,完成回調函數的執行。node
取消事件時,根據取消事件的名,找到對應的對象,刪掉。 若是沒有傳事件名, callbacks
所有刪除。git
上面的分析是一個事件監聽器最基本的功能,如今分析一下其餘的狀況和功能github
問題1: 註冊了一個事件,綁定一個回調函數,若是針對這個事件綁定多個回調函數,怎麼辦,並且在這種狀況下觸發順序是要根據綁定前後順序依次執行,怎麼能保證這個順序問題。bash
# 解決思路
// 第一次註冊onClick事件, 回調函數handle1, callbacks對象是這樣的:
{
onClick: {
next: {
callback: handle1,
context: undefined,
next: {}
}
}
}
// 第二次:
{
onClick: {
next: {
callback: handle1,
context: undefined,
next: {
callback: handle2,
context: undefined,
next: {}
}
}
}
}
// 以此類推...
當在觸發的時候, 根據這個對象,依次從外往裏層層執行
思路和方向是這樣的,具體怎麼實現,後面看源碼就一目瞭然
複製代碼
問題2: 只監聽一次的需求怎麼實現,意思是,註冊一個事件,而後一旦觸發,就取消掉這個事件的監聽,再沒機會觸發。app
# 解決思路
註冊一個事件,綁定一個對應的函數。
一樣的思路,你註冊一個事件,完成一個回調任務A, 在內部我保證觸發的時候完成你的回調任務A的同時,我多作一件事(取消), 即從新包裝一個新的回調給到註冊函數
const wrapper = (...args) => {
callback.apply(this, args)
this.off(events, wrapper, context)
}
this.on(events, wrapper, context)
複製代碼
問題3: 取消事件的時候,若是我要實現只是取消一個事件裏某個回調handle
,意思是,一個事件可能綁定handle1
、handle2
, 如今作到觸發時不要執行handle2
, 怎麼思路?函數
# 解決思路
若是是這種狀況,能夠把handle2忽略掉,把關注點放在剩下的handle1,即把這些剩下的,沒取消的handle都從新的註冊一遍
// 這個判斷做用,過濾被取消的handle, 其餘的從新註冊一遍
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx)
}
複製代碼
理解完上面的思路,對下面的源碼,看起來就很順暢。 思路同樣,實現方式能夠有多種,只是做者的寫法也很值得學習。學習
class Events {
constructor (opts) {
if (typeof opts !== 'undefined' && opts.callbacks) {
this.callbacks = opts.callbacks
} else {
this.callbacks = {}
}
}
on (events, callback, context) {
let calls, event, node, tail, list
if (!callback) {
return this
}
events = events.split(Events.eventSplitter)
calls = this.callbacks
while ((event = events.shift())) {
list = calls[event]
node = list ? list.tail : {}
node.next = tail = {}
node.context = context
node.callback = callback
calls[event] = {
tail,
next: list ? list.next : node
}
}
return this
}
once (events, callback, context) {
const wrapper = (...args) => {
callback.apply(this, args)
this.off(events, wrapper, context)
}
this.on(events, wrapper, context)
return this
}
off (events, callback, context) {
let event, calls, node, tail, cb, ctx
if (!(calls = this.callbacks)) {
return this
}
if (!(events || callback || context)) {
delete this.callbacks
return this
}
events = events ? events.split(Events.eventSplitter) : Object.keys(calls)
while ((event = events.shift())) {
node = calls[event]
delete calls[event]
if (!node || !(callback || context)) {
continue
}
tail = node.tail
while ((node = node.next) !== tail) {
cb = node.callback
ctx = node.context
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx)
}
}
}
return this
}
trigger (events) {
let event, node, calls, tail, rest
if (!(calls = this.callbacks)) {
return this
}
events = events.split(Events.eventSplitter)
rest = [].slice.call(arguments, 1)
while ((event = events.shift())) {
if ((node = calls[event])) {
tail = node.tail
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest)
}
}
}
return this
}
}
Events.eventSplitter = /\s+/
export default Events
複製代碼
Events.eventSplitter = /\s+/
、 events = events.split(Events.eventSplitter)
這個的做用是, 若是事件名是'onClick onTouch'
這樣的狀況, 是可以對2種自定義事件完成註冊