Zepto源碼分析-callbacks模塊

//     Zepto.js
//     (c) 2010-2015 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.

/**
 * 回調函數管理:添加add() 移除remove()、觸發fire()、鎖定lock()、禁用disable()回調函數。它爲Deferred異步隊列提供支持
 * 原理:經過一個數組保存回調函數,其餘方法圍繞此數組進行檢測和操做
 *
 *
 *  標記:
 *      once: 回調只能觸發一次
 *      memory 記錄上一次觸發回調函數列表時的參數,以後添加的函數都用這參數當即執行
 *      unique  一個回調函數只能被添加一次
 *      stopOnFalse 當某個回調函數返回false時中斷執行
 */
;(function($){
  // Create a collection of callbacks to be fired in a sequence, with configurable behaviour
  // Option flags:
  //   - once: Callbacks fired at most one time.
  //   - memory: Remember the most recent context and arguments
  //   - stopOnFalse: Cease iterating over callback list
  //   - unique: Permit adding at most one instance of the same callback
  $.Callbacks = function(options) {
    options = $.extend({}, options)

    var memory, // Last fire value (for non-forgettable lists)
        fired,  // Flag to know if list was already fired    //是否回調過
        firing, // Flag to know if list is currently firing  //回調函數列表是否正在執行中
        firingStart, // First callback to fire (used internally by add and fireWith) //第一回調函數的下標
        firingLength, // End of the loop when firing   //回調函數列表長度?
        firingIndex, // Index of currently firing callback (modified by remove if needed)
        list = [], // Actual callback list     //回調數據源: 回調列表
        stack = !options.once && [], // Stack of fire calls for repeatable lists//回調只能觸發一次的時候,stack永遠爲false

        /**
         * 回調底層函數
         */
        fire = function(data) {
          memory = options.memory && data   //記憶模式,觸發事後,再添加新回調,也當即觸發。
          fired = true
          firingIndex = firingStart || 0
          firingStart = 0
          firingLength = list.length
          firing = true      //標記正在回調

            //遍歷回調列表
          for ( ; list && firingIndex < firingLength ; ++firingIndex ) {
              //若是 list[ firingIndex ] 爲false,且stopOnFalse(中斷)模式
              //list[firingIndex].apply(data[0], data[1])  這是執行回調
            if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
              memory = false  //中斷回調執行
              break
            }
          }
          firing = false //回調執行完畢
          if (list) {
              //stack裏還緩存有未執行的回調
            if (stack) stack.length && fire(stack.shift())  //執行stack裏的回調
            else if (memory) list.length = 0 //memory 清空回調列表    list.length = 0清空數組的技巧
            else Callbacks.disable()             //其餘狀況如  once 禁用回調
          }
        },

        Callbacks = {
          //添加一個或一組到回調列表裏
          add: function() {
            if (list) {        //回調列表已存在
              var start = list.length,   //位置從最後一個開始
                  add = function(args) {
                    $.each(args, function(_, arg){
                      if (typeof arg === "function") {    //是函數
                          //非unique,或者是unique,但回調列表未添加過
                        if (!options.unique || !Callbacks.has(arg)) list.push(arg)
                      }
                      //是數組/僞數組,添加,從新遍歷
                      else if (arg && arg.length && typeof arg !== 'string') add(arg)
                    })
                  }

                //添加進列表
              add(arguments)

                //若是列表正在執行中,修正長度,使得新添加的回調也能夠執行
              if (firing) firingLength = list.length
              else if (memory) {
                  //memory 模式下,修正開始下標,
                firingStart = start
                fire(memory)         //當即執行全部回調
              }
            }
            return this
          },

            //從回調列表裏刪除一個或一組回調函數
          remove: function() {
            if (list) {       //回調列表存在才能夠刪除
                //_做廢參數
                //遍歷參數
              $.each(arguments, function(_, arg){
                var index

                  //若是arg在回調列表裏
                while ((index = $.inArray(arg, list, index)) > -1) {
                  list.splice(index, 1)                                //執行刪除
                  // Handle firing indexes
                    //回調正在執行中
                  if (firing) {
                      //避免回調列表溢出
                    if (index <= firingLength) --firingLength  //在正執行的回調函數後,遞減結尾下標
                    if (index <= firingIndex) --firingIndex     //在正執行的回調函數前,遞減開始下標
                  }
                }
              })
            }
            return this
          },

            /**
             * 檢查指定的回調函數是否在回調列表中
             * @param fn
             * @returns {boolean}
             */
          has: function(fn) {
              //
            return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length))
          },

            /**
             * 清空回調函數
             * @returns {*}
             */
          empty: function() {
            firingLength = list.length = 0
            return this
          },

            //禁用回調函數
          disable: function() {
            list = stack = memory = undefined
            return this
          },

            /**
             * 是否已禁用回調函數
             * @returns {boolean}
             */
          disabled: function() {
            return !list
          },
            /**
             * 鎖定回調函數
             * @returns {*}
             */
          lock: function() {
            stack = undefined;   //致使沒法觸發

             //非memory模式下,禁用列表
            if (!memory) Callbacks.disable()
            return this
          },
            /**
             * 是不是鎖定的
             * @returns {boolean}
             */
          locked: function() {
            return !stack
          },

            /**
             * 用上下文、參數執行列表中的全部回調函數
             * @param context
             * @param args
             * @returns {*}
             */
          fireWith: function(context, args) {
                // 未回調過,非鎖定、禁用時
            if (list && (!fired || stack)) {

              args = args || []
              args = [context, args.slice ? args.slice() : args]
              if (firing) stack.push(args)  //正在回調中  ,存入static

              else fire(args) //不然當即回調
            }
            return this
          },

            /**
             * 用參數執行列表中的全部回調函數
             * @param context
             * @param args
             * @returns {*}
             */
          fire: function() {
                //執行回調
            return Callbacks.fireWith(this, arguments)
          },

            /**
             * 回調列表是否被回調過
             * @returns {boolean}
             */
          fired: function() {
            return !!fired
          }
        }

    return Callbacks
  }
})(Zepto)

  

 

 用法html

  自己單獨用於管理回調函數列表。數組

    另外做爲Deferred異步隊列的基礎。瀏覽器

     

      

 生命週期緩存

 

 

設計原理多線程

    

 

   設計上的疑問app

         列表觸發列表stack是基於,當列表正在觸發函數,而又需正執行添加函數的操做。這意味着兩個線程,線程A在觸發fire列表,線程B往列表add函數。理論上這樣設計是合理的。異步

        但實際上,咱們分析下...函數

        瀏覽器解析頁面的主要線程以下oop

  •     JS引擎線程
  •     GUI渲染線程 和JS互斥,緣由是JS操做DOM
  •     瀏覽器事件觸發線程
  •     計時線程
  •     HTTP請求線程  

   因爲JS引擎是單線程的,任何JS的執行一個時間片斷只能執行一段代碼。如setTimeout,雖然開闢了計時線程,可是一旦響應時間到了,將執行JS函數時,馬上遵循單線程原則。函數塞入執行隊列。事件線程也同樣。一旦響應時,JS響應函數也會塞入執行隊列。 問題來了,回調列表Callbacks怎麼可能在觸發fire的同時,再add操做呢?只有真正的多線程才能碰到這樣的問題。this

     Worker?Work它是沒法訪問本頁面的$.Callbacks對象。由於它訪問不了window對象。

   除非它本身再經過importScripts('zepto-callbacks.js')加載一個JS,但它和頁面的Callbacks是兩個不一樣的對象。頁面向Worker發送數據,Worker加載新的Callbacks,執行完畢後,再返回數據給頁面。這多麼麻煩啊?(Worker用法

      因此Callbacks的這個多線程設計到底是基於什麼場景?

 

舉例

此觀察者案例來自  http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html

// 觀察者模式
var observer = {
    hash: {},
    subscribe: function(id, callback) {
        if (typeof id !== 'string') {
            return
        }
        if (!this.hash[id]) {
            this.hash[id] = $.Callbacks()
            this.hash[id].add(callback)
        } else {
            this.hash[id].add(callback)
        }
    },
    publish: function(id) {
        if (!this.hash[id]) {
            return
        }
        this.hash[id].fire(id)
    }
}
 
// 訂閱
observer.subscribe('mailArrived', function() {
    alert('來信了')
})
observer.subscribe('mailArrived', function() {
    alert('又來信了')
})
observer.subscribe('mailSend', function() {
    alert('發信成功')
})
 
// 發佈
setTimeout(function() {
    observer.publish('mailArrived')
}, 5000)
setTimeout(function() {
    observer.publish('mailSend')
}, 10000)
相關文章
相關標籤/搜索