讀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 博客倉庫

相關文章
相關標籤/搜索