读Taro消息机制源码笔记

1,836 阅读4分钟

使用

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,意思是,一个事件可能绑定handle1handle2, 现在做到触发时不要执行handle2, 怎么思路?

    # 解决思路
    如果是这种情况,可以把handle2忽略掉,把关注点放在剩下的handle1,即把这些剩下的,没取消的handle都重新的注册一遍
    
    // 这个判断作用,过滤被取消的handle, 其他的重新注册一遍
    if ((callback && cb !== callback) || (context && ctx !== context)) {
        this.on(event, cb, ctx)
    }
    

理解完上面的思路,对下面的源码,看起来就很顺畅。 思路一样,实现方式可以有多种,只是作者的写法也很值得学习。

源码代码

Taro-Events源码地址

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种自定义事件完成注册

GITHUB 博客仓库