读Taro消息机制源码笔记

使用

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

    # 解决思路
    若是是这种状况,能够把handle2忽略掉,把关注点放在剩下的handle1,即把这些剩下的,没取消的handle都从新的注册一遍
    
    // 这个判断做用,过滤被取消的handle, 其余的从新注册一遍
    if ((callback && cb !== callback) || (context && ctx !== context)) {
        this.on(event, cb, ctx)
    }
    复制代码

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

源码代码

Taro-Events源码地址ui

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 博客仓库

相关文章
相关标签/搜索