• 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)
    

      

     用法

      本身单独用于管理回调函数列表。

        另外作为Deferred异步队列的基础。

         

          

     生命周期

    设计原理

        

       设计上的疑问

             列表触发列表stack是基于,当列表正在触发函数,而又需正执行添加函数的操作。这意味着两个线程,线程A在触发fire列表,线程B往列表add函数。理论上这样设计是合理的。

            但实际上,我们分析下...

            浏览器解析页面的主要线程如下

    •     JS引擎线程
    •     GUI渲染线程 和JS互斥,原因是JS操作DOM
    •     浏览器事件触发线程
    •     计时线程
    •     HTTP请求线程  

       由于JS引擎是单线程的,任何JS的执行一个时间片段只能执行一段代码。如setTimeout,虽然开辟了计时线程,但是一旦响应时间到了,将执行JS函数时,立刻遵循单线程原则。函数塞入执行队列。事件线程也一样。一旦响应时,JS响应函数也会塞入执行队列。 问题来了,回调列表Callbacks怎么可能在触发fire的同时,再add操作呢?只有真正的多线程才能碰到这样的问题。

         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)
  • 相关阅读:
    CentOS7安装MySql5.7
    环境变量配置
    Spring 注解
    MySQL
    常用命令
    Android Studio & IntelliJ IDEA常见问题与设置
    order by、group by、having的区别
    把WebStrom添加到右键菜单
    解决github访问速度慢的问题
    docker修改时区
  • 原文地址:https://www.cnblogs.com/mominger/p/4369469.html
Copyright © 2020-2023  润新知