使用
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()
初步分析功能要点
- 注册监听事件: 自定义事件名,指定事件触发执行函数,可以指定上下文
- 触发事件: 指定触发的事件名,可传入相关参数
- 取消事件监听: 可取消指定事件,可取消所有事件
初步解析
大致看下源码,有一个整体观,切记一上来就一股脑钻进某个细节里。
-
从源码里,可以大概知道,作者是通过
class面向对象的方式实现,内部管理一个callbacks对象,注册一个事件,往对象里添加一个事件属性对象,事件属性对象下有几个属性,一个是事件回调函数callback,一个是执行上下文context,还有一个next, 作用是多个callback对象嵌套。 -
触发事件时,通过事件名,找到对应的对象,完成回调函数的执行。
-
取消事件时,根据取消事件的名,找到对应的对象,删掉。 如果没有传事件名,
callbacks全部删除。
二次分析
上面的分析是一个事件监听器最基本的功能,现在分析一下其他的情况和功能
-
问题1: 注册了一个事件,绑定一个回调函数,如果针对这个事件绑定多个回调函数,怎么办,而且在这种情况下触发顺序是要根据绑定先后顺序依次执行,怎么能保证这个顺序问题。
# 解决思路 // 第一次注册onClick事件, 回调函数handle1, callbacks对象是这样的: { onClick: { next: { callback: handle1, context: undefined, next: {} } } } // 第二次: { onClick: { next: { callback: handle1, context: undefined, next: { callback: handle2, context: undefined, next: {} } } } } // 以此类推... 当在触发的时候, 根据这个对象,依次从外往里层层执行 思路和方向是这样的,具体怎么实现,后面看源码就一目了然 -
问题2: 只监听一次的需求怎么实现,意思是,注册一个事件,然后一旦触发,就取消掉这个事件的监听,再没机会触发。
# 解决思路 注册一个事件,绑定一个对应的函数。 同样的思路,你注册一个事件,完成一个回调任务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种自定义事件完成注册