你的位置:首页 > Java教程

[Java教程]zepto源码研究


简要:$.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-----test1-----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();// 方式1callbacks.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