zepto源码研究 - callback.js

简要:$.Callbacks是一个生成回调管家Callback的工厂,Callback提供一系列方法来管理一个回调列表($.Callbacks的一个私有变量list),包括添加回调函数,

删除回调函数等等...,话不多说看正文:

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

 memory的值由传入$.Callbacks的形参对象决定,具有状态记忆功能。当为null||false时,callback.add仅仅是添加方法,而当为true时,则添加之后会立即执行。

请参考如下代码(来自: http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
 function fn1() {
      console.log(1)
 }
  function fn2() {
      console.log(2)
  }
  var callbacks = $.Callbacks(‘memory‘);
  callbacks.add(fn1);
  callbacks.fire(); // 必须先fire
  callbacks.add(fn2); // 此时会立即触发fn2

如下是触发回调任务的底层函数:

fire = function(data) {
          memory = options.memory && data   //记忆模式,触发过后,再添加新回调,也立即触发。
          fired = true
          firingIndex = firingStart || 0    //回调任务开始的索引赋值给将要执行函数的索引
          firingStart = 0                   //回调任务的触发会将列表里剩下的所有函数执行,因此下一次任务触发肯定是从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里还缓存有未执行的回调,如果回调任务只能执行一次则stack为false
            if (stack) stack.length && fire(stack.shift())  //执行stack里的回调
            else if (memory) list.length = 0 //memory 清空回调列表    list.length = 0清空数组的技巧
            else Callbacks.disable();             //其他情况如  once 禁用回调
          }
        },

fire方法在回调执行前首先初始化回调索引和回调状态,然后循环顺序执行回调函数,传递参数为data[1],以data[0]为上下文执行,执行完后根据once是否只执行一次

,处理和回收list,memory,stack等变量。

var ca = {
  name:"tom",
  age:1
}
function printName() {
  console.log(this.name+"-----"+arguments[0]);
}
function printAge() {
  console.log(this.age+"-----"+arguments[0]);
}
list.push(printName);
list.push(printAge);
fire([ca,‘test‘]);
结果:
tom-----test
1-----test

接下来是创建了一个Callbacks 对象以及一系列方法

//添加一个或一组到回调列表里
          add: function() {
            if (list) {        //回调列表已存在
              var start = list.length,   //位置从最后一个开始
                  add = function(args) {  //参数可以是:fn,[fn,fn],fn
                    $.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)

              //如果列表正在执行中,修正长度,使得新添加的回调也可以执行,
              //firing:true表明fire中的循环执行还未结束,此时可以修改length;为false则表示循环执行结束了
              if (firing) firingLength = list.length
              else if (memory) {
                //memory 模式下,修正开始下标,start为list.length,这里只循环一次
                firingStart = start
                fire(memory)         //立即执行所有回调
              }
            }
            return this
          }

add方法是将一系列的fn加入到回调列表中,内部的add方法用到了递归技巧,同时对于回调任务的执行期间做出相应处理

,如下是例子(参考链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)

function fn1() {
    console.log(1)
}
function fn2() {
    console.log(2)
}

var callbacks = $.Callbacks();
// 方式1
callbacks.add(fn1);
// 方式2 一次添加多个回调函数
callbacks.add(fn1, fn2);
// 方式3 传数组
callbacks.add([fn1, fn2]);
// 方式4 函数和数组掺和
callbacks.add(fn1, [fn2]);

remove方法会从回调列表中删除一个或一组fn(有去重功能)

//从回调列表里删除一个或一组回调函数,remove(fn),remove(fn,fn)
          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
          }

方法中的while循环是去除回调列表中重复的函数的技巧,去除指定fn之后,如果此时回调列表正在执行回调任务,则修正回调索引(因为list中所有的回调函数的索引都改变了),这里能传多个fn做参数,它会循环删除,如下例子(参考链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)

function fn1() {
    console.log(1)
}
function fn2() {
    console.log(2)
}
var callbacks = $.Callbacks();
callbacks.add(fn1, fn2);
callbacks.remove(fn1);
//此时fire只会触发fn2了。

var callbacks = $.Callbacks();
callbacks.add(fn1, fn2, fn1, fn2);
callbacks.remove(fn1);
//此时会把add两次的fn1都删掉,fire时只触发fn2两次。换成if则只删fn1一次

Callbacks里面的函数封装了fire方法,Callbacks.fire(args),回调函数将以Callbacks为this,args为参数调用,stack的作用是若回调列表处于触发状态,此时将

本次要触发的任务信息存入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)
          }

如下:(参考链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)

function fn() {
    console.log(this); // 上下文是callbacks
    console.log(arguments); // [3]
}
var callbacks = $.Callbacks();
callbacks.add(fn);
callback.fire(3);
//前面已经提到了,fire方法用来触发回调函数,默认的上下文是callbacks对象,
//还可以传参给回调函数。
function fn() {
    console.log(this); // 上下文是person
    console.log(arguments); // [3]
}
var person = {name: ‘jack‘};
var callbacks = $.Callbacks();
callbacks.add(fn);
callback.fireWith(person, 3); //callback.fire.call(person, 3);
//其实fire内部调用的是fireWith,只是将上下文指定为this了,
//而this正是$.Callbacks构造的对象。

设计思考:

add,remove方法和fire等方法内部都加入了对回调列表的状态的判断和相应处理,比如fireWith方法内部判断当前回调任务是否正在进行,如果是,则将要执行的fn暂时加入到stack中。但这一设计思路对于单线程的js来说有点不太合理,如果是先执行回调列表,再fireWith,则实际过程是回调任务执行完之后再执行fireWith,这个时候回调任务已经结束了,不可能存在firing的情况。但为何jser还是要这么设计呢?

这里的设计是针对多线程来设计的。回调任务开始的同时,容许另一线程操作并修改回调列表内容。假想这样一个场景:有一个网络机器人R,功能是执行所有线上客户要求执行的一系列操作,某一时刻,客户A上传了一系列操作(命名为DO),当R正在执行操作时,客户A要求此时执行DO,而R正在执行其他操作,此时,R是被锁住的。DO则被加入到缓冲队列中延后执行。

这里在举个例子说明  来自链接: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)

结束语:本文素材多来自其他文章并加上了自己的理解,感谢如下两位博客:

http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html

http://www.cnblogs.com/mominger/p/4369469.html

时间: 2024-10-11 07:00:44

zepto源码研究 - callback.js的相关文章

zepto源码研究 - deferred.js(jquery-deferred.js)

简要:zepto的deferred.js 并不遵守promise/A+ 规范,而在jquery v3.0.0中的defer在一定程度上实现了promise/A+ ,因此本文主要研究jquery v3.0.0中的defer. 首先   在上源码前,本人觉得有必要认识一下promise/A+ 规范:https://segmentfault.com/a/1190000002452115 接下来上源码: define( [ "./core", "./var/slice",

zepto源码研究 - fx_methods.js

简要:依赖fx.js,主要是针对show,hide,fadeIn,fadeOut的封装. 源码如下: // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT license. ;(function($, undefined){ var document = window.document, docElem = document.documentElement, or

zepto源码研究 - zepto.js - 6(模板方法)

width  height  模板方法   读写width/height ['width', 'height'].forEach(function(dimension){ //将width,hegiht转成Width,Height,用于document获取 var dimensionProperty = dimension.replace(/./, function(m){ return m[0].toUpperCase() }) $.fn[dimension] = function(value

zepto源码研究 - zepto.js-4(常用的工具)

$.each: /** * 以集合每一个元素作为上下文,来执行回调函数 * @param elements * @param callback * @returns {*} */ $.each = function(elements, callback){ var i, key if (likeArray(elements)) { //数组.伪数组 for (i = 0; i < elements.length; i++) if (callback.call(elements[i], i, el

underscore.js源码研究(8)

概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以就了结研究underscore源码这一心愿吧. underscore.js源码研究(1) underscore.js源码研究(2) underscore.js源码研究(3) underscore.js源码研究(4) underscore.js源码研究(5) underscore.js源码研究(6)

Zepto源码分析之二~三个API

由于时间关系:本次只对这三个API($.camelCase.$.contains.$.each)方法进行分析 第一个方法变量转驼峰:$.camelCase('hello-world-welcome'); 源码: var camelize; /** * 字符串替换 * 使用replace第二个参数带回调 */ camelize = function(str) { return str.replace(/-+(.)?/g, function(match, chr) { return chr ? ch

读 zepto 源码之工具函数

Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.extend 方法可以用来扩展目标对象的属性.目标对象的同名属性会被源对象的属性覆盖. $.extend 其实调用的是内部方法 extend, 所以我们先看看内部方法 extend 的具体实现. function extend(target, source, deep) { for (key in source) // 遍历源对象的属性值 if (deep && (i

【JavaScript】$.extend使用心得及源码研究

最近写多了js的面向对象编程,用$.extend写继承写得很顺手.但是在使用过程中发现有几个问题. 1.深拷贝 $.extend默认是浅拷贝,这意味着在继承复杂对象时,对象中内嵌的对象无法被拷贝到. 因此如果要深拷贝,则需要将第一个参数设置为true. 如: var a = { a:1 }; var b = { b:{c:1} }; $.extend(a,b); a = { a:1,b:{c:1} }; 2.对象覆盖 在进行面向对象编程时,有这么一种情况. 比如有一个公共对象,某个类在实例化的时

自写图片遮罩层放大功能jquery插件源码,photobox.js 1.0版,不兼容IE6

阿嚏~~~ 话说本屌丝没啥开发插件的经验,但是天公不作美,公司需要让我自己开发个图片放大的插件 但公司老大的话,犹如吾皇之圣旨,微臣必当肝脑涂地,莫敢不从啊~~~ 于是乎,作为一个超级小白,本人只能瞎研究了,幸好黑天不负屌丝人,本屌丝终于搞出来了,虽然不尽善尽美,但是功能还是可以用的啦 先附上源码,求各种大神指导: /******************************* * photobox跨浏览器兼容插件 v1.0(不支持IE6) * 格式:<a href="big.jpg&q