在上一篇jQuery.Callbacks之demo主要说了Callbacks对象初始化常见的选项,这一篇主要分析下Callbacks对象的源代码,对给出两个较为繁琐的demo

 // String to Object options format cache
var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache
/*
这个函数主要将传入的options字符串封装成对象
比如将传入的'once memory'封装成
optionsCache['once memory'] = {
once : true,
memory : true
}
这样方便下次同样的options复用和判断
*/
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.split( core_rspace ), function( _, flag ) {
object[ flag ] = true;
});
return object;
} /*
* Create a callback list using the following parameters:
*
* options: an optional list of space-separated options that will change how
* the callback list behaves or a more traditional option object
*
* By default a callback list will act like an event callback list and can be
* "fired" multiple times.
*
* Possible options:
*
* once: will ensure the callback list can only be fired once (like a Deferred)
*
* memory: will keep track of previous values and will call any callback added
* after the list has been fired right away with the latest "memorized"
* values (like a Deferred)
*
* unique: will ensure a callback can only be added once (no duplicate in the list)
*
* stopOnFalse: interrupt callings when a callback returns false
*
*/
jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options ); var // Last fire value (for non-forgettable lists)
//大多数情况下这个变量是包含两个元素的数组,[0]表示上次调用的对象,[1]表示上次调用的参数
memory,
// Flag to know if list was already fired
//标识是否执行过回调函数,主要用来实现once
fired,
// Flag to know if list is currently firing
//当前是否在firing,可以参考多线编程中锁的概念,主要用在调用回调函数时,对callbacks对象进行add、remove或者fire,后面会有两个单独的例子说明这种情况
firing,
// First callback to fire (used internally by add and fireWith)
firingStart,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// Actual callback list
//所有的回调会被push到这个数组
list = [],
// Stack of fire calls for repeatable lists
//结合firing使用,如果有once选项没什么作用,否则当firing为true时将add或者fire的操作临时存入这个变量,以便于循环完list时继续处理这个变量里面的函数队列
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
//如果设置memory为true,则将本次的参数data缓存到memory中,用于下次调用
memory = options.memory && data;
fired = true;
//如果options.memory为true,firingStart为上一次Callbacks.add后回调列表的length值
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
//如果stopOnFalse为true且本次执行的回调函数返回值为false,则终止回调函数队列的执行
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
//设置memory为false,防止调用add时会被fire(这个分支是在stopOnFalse memory时被触发)
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
//options.once为false(stack的作用见上)
if ( stack ) {
//存在递归的可能,所以不用使用while
if ( stack.length ) {
fire( stack.shift() );
}
//memory = true, memory = true的情况
} else if ( memory ) {
list = [];
} else {
//once = true, memory = false的情况
self.disable();
}
}
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
// First, we save the current length
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
//实现unique(回调不唯一 或 唯一且不存在,则push)
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
//如果arg是数组,递归添加回调
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
//如果memory不是false,则直接每次add的时候都自动fire
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
// Remove a callback from the list
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
//如果在执行Callbacks.remove操作的状态为firing时则更新firingLength和firingIndex的值
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
//特殊处理,如果移除的回调的索引小于当前正在执行回调的索引,则firingIdex--
//后面未执行的回调则得以正常执行
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Control if a given callback is in the list
has: function( fn ) {
return jQuery.inArray( fn, list ) > -1;
},
// Remove all callbacks from the list
empty: function() {
list = [];
return this;
},
// Have the list do nothing anymore
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// Is it locked?
locked: function() {
return !stack;
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
if ( list && ( !fired || stack ) ) {
if ( firing ) {
stack.push( args );
} else {
fire( args );
}
}
return this;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
}; return self;
};

  需要特殊注意的是有一个firing这个变量,下面给出这个变量的应用场景:

  1、在Callbacks.add中firing为true的情况

 // 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3
function fn1(val){
console.log( 'fn1 says ' + val );
//此时Callbacks函数内部的firingLength会自动加1,虽然初始化的Callbacks对象有memory选项,
//但add并不会立即执行fn2,而是等执行完add前的函数队列之后再执行fn2
cbs.add(fn2);
}
function fn2(val){
console.log( 'fn2 says ' + val );
}
function fn3(val){
console.log( 'fn3 says ' + val );
} // Callbacks传递了memory
// 也可以这样使用$.Callbacks({ memory: true });
var cbs = $.Callbacks('memory'); // 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2
cbs.add(fn1); //fn1 says foo
//fn2 says foo
cbs.fire('foo'); //将之前fire的参数传递给最近增加的回调fn3,并执行fn3
//fn3 says foo
cbs.add(fn3); //再执行一次fire,注意此时回调列表中的回调依次是fn1,fn2,fn3,fn2
//fn1 says bar
//fn2 says bar
//fn3 says bar
//fn2 says bar
cbs.fire('bar');

  2、在Callbacks.fireWith中firing为true的情况

function fn1(val){
console.log( 'fn1 says ' + val );
}
function fn2(val){
console.log( 'fn2 says ' + val );
//此时并不会立即触发cbs里面的回调,而是先把[window, ['bar']]放入stack里面
//等执行完fireWith前的函数队列之后才执行
cbs.fireWith(window, ['bar']);
//firingLength会减一,一定要将当前的函数remove掉,否则会导致死循环
cbs.remove(fn2);
} var cbs = $.Callbacks();
cbs.add(fn1);
cbs.add(fn2);
//fn1 says bar
//fn2 says bar
//fn1 says bar
cbs.fire('bar');

jQuery.Callbacks之源码解读的更多相关文章

  1. jQuery.data() 与 jQuery(elem).data()源码解读

    之前一直以为 jQuery(elem).data()是在内部调用了 jQuery.data(),看了代码后发现不是.但是这两个还是需要放在一起看,因为它们内部都使用了jQuery的数据缓存机制.好吧, ...

  2. 亚马逊左侧菜单延迟z三角 jquery插件jquery.menu-aim.js源码解读

    关于亚马逊的左侧菜单延迟,之前一直不知道它的实现原理.梦神提到了z三角,我也不知道这是什么东西.13号那天很有空,等领导们签字完我就可以走了.下午的时候,找到了一篇博客:http://jayuh.co ...

  3. jquery判断数据类型源码解读

    var class2type = {}; ("Boolean Number String Function Array Date RegExp Object Error").spl ...

  4. jQuery.Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  5. 第二十五课:jQuery.event.trigger的源码解读

    本课主要来讲解jQuery.event.trigger的源码解读. trigger = function(event, data, elem, onlyHandlers){ if(elem & ...

  6. 第二十四课:jQuery.event.remove,dispatch的源码解读

    本课还是来讲解一下jQuery是如何实现它的事件系统的.这一课我们先来讲一下jQuery.event.remove的源码解读. remove方法的目的是,根据用户传参,找到事件队列,从里面把匹配的ha ...

  7. 第二十三课:jQuery.event.add的原理以及源码解读

    本课主要来讲解一下jQuery是如何实现它的事件系统的. 我们先来看一个问题: 如果有一个表格有100个tr元素,每个都要绑定mouseover/mouseout事件,改成事件代理的方式,可以节省99 ...

  8. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  9. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

随机推荐

  1. Windows程序设计(第五版)学习:第四章 文本输出

    第四章 文本输出 1,客户区:整个应用程序窗口中没有被标题栏.边框.菜单栏.工具栏.状态栏和滚动条占用的区域.简而言之,客户区就是窗口中程序可以在上面绘制并向用户传达可视化信息的区域.   2,大多数 ...

  2. JStorm第一个程序WordCount详解

    一.Strom基本知识(回顾) 1,首先明确Storm各个组件的作用,包括Nimbus,Supervisor,Spout,Bolt,Task,Worker,Tuple nimbus是整个storm任务 ...

  3. CSS-页面布局

    介绍 几个实现多栏布局的方法.主要介绍使用内部div来创建浮动的栏. 多栏布局有三种基本的实现方案:固定宽度.流动.弹性. 固定宽度布局的大小是随用户调整浏览器窗口大小而变化,一般是900至1100像 ...

  4. VC++ 关于 ON_UPDATE_COMMAND_UI 相关的作用.

    ON_COMMAND_RANGE(ID_SORTING_GROUPBYTYPE, ID_SORTING_SORTBYACCESS, OnSort) ON_UPDATE_COMMAND_UI_RANGE ...

  5. input输入子系统

    一.什么是input输入子系统? 1.Linux系统支持的输入设备繁多,例如键盘.鼠标.触摸屏.手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型.不同原理.不同的输入信 ...

  6. flume远程调试

    项目开发的时候,出现问题的时候,通常在IDE里面直接进行调试,但有时候我们可能用的是另外的一些开源框架,甚至运行程序里面没有一行代码是我们自己写的,如果出现一些较复杂的问题,那么我们可能就会用到远程调 ...

  7. Selenium2+python自动化14-iframe

    前言 有很多小伙伴在拿163作为登录案例的时候,发现不管怎么定位都无法定位到,到底是什么鬼呢,本篇详细介绍iframe相关的切换 以http://mail.163.com/登录页面10为案例,详细介绍 ...

  8. wcf session开启

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] pu ...

  9. C# random生成随机数全部一样

    最近做排序测试  使用random生成随机数全部一样 估计是因为random采用的随机种子为时间戳 而一个循化执行消耗的时间没有到时间戳的最小单位 故没有变化 Thread.Sleep(10); 使用 ...

  10. 一个使用高并发高线程数 Server 使用异步数据库客户端造成的超时问题

    现象 今天在做一个项目时, 将 tomcat 的 maxThreads 加大, 加到了 1024, tomcat 提供的服务主要是做一些运算, 然后插入 redis, 查询 redis, 最后将任务返 ...