前言:

作为参数传递给另一个函数执行的函数我们称为回调函数,那么该回调又是否是异步的呢,何谓异步,如:作为事件处理器,或作为参数传递给

(setTimeout,setInterval)这样的异步函数,或作为ajax发送请求,应用于请求各种状态的处理,我们可以称为异步回调,jQuery.Callbacks

为我们封装了一个回调对象模块,我们先来看一个应用场景:

// 为什么jQuery中的ready事件可以执行多个回调,这得益于我们的jQuery.Deferred递延对象(是基于jQuery.Callbacks回调模块)
jQuery(function($) {
console.log('document is ready!');
// do something
}); jQuery(function($) {
// do something
}); // 实现原型
// jQuery.Deferred版代码
var df = jQuery.Deferred();
df.resolve(); // 在ready事件中调用 // 可以多次执行绑定的函数
df.done(fn1, fn2, fn3);
df.done(fn4);
// ... // jQuery.Callbacks版代码
var cb = jQuery.Callbacks('once memory');
cb.fire(); // 在ready事件中调用 // 可以多次执行绑定的函数
cb.add(fn1, fn2, fn3);
cb.add(fn4);
// ...

现在我们知道jQuery中的ready事件是可以这样执行多个回调的,要想深入理解其源码,让我们继续看下面吧

jQuery回调、递延对象总结篇索引:

jQuery回调、递延对象总结(上篇)—— jQuery.Callbacks

jQuery回调、递延对象总结(中篇) —— 神奇的then方法

jQuery回调、递延对象总结(下篇) —— 解密jQuery.when方法

一、jQuery.Callbacks设计思路

使用一个私有变量list(数组)存储回调,执行jQuery.Callbacks函数将返回一个可以操作回调列表list的接口对象,
而传入jQuery.Callbacks函数的options参数则用来控制返回的回调对象操作回调列表的行为

回调对象中的方法

{
add: 增加回调到list中
remove: 从list中移除回调
fire: 触发list中的回调
fired: 回调对象是否执行过fire方法
fireWith: 触发list中的回调,第一个参数为执行域
has: 判断函数是否在list中
empty: 将list致空,list = [];
lock: 锁定list
locked: 是否锁定
disable: 禁用回调对象
disabled: 是否禁用
}

参数标志:

options = {
once: 回调对象仅触发(fire)一次 memory: 跟踪记录每一次传递给fire函数的参数,在回调对象触发后(fired),
将最后一次触发(fire)时的参数(value)传递给在add操作后即将被调用的回调 unique: 在add操作中,相同的函数仅只一次被添加(push)到回调列表中 stopOnFalse:当回调函数返回false,中断列表中的回调循环调用,且memory === false,阻止在add操作中将要触发的回调
}

二、源码解析

var // Used for splitting on whitespace
core_rnotwhite = /\S+/g; var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache
// 将字符串格式选项转化成对象格式形式,并存储在缓存对象optionsCache[options]中
// 该缓存起作用适用于执行多次jQuery.Callbacks函数,且传递options参数一致,我们在jQuery.Deferred
// 源码就可以看到tuples二维数组中执行了两次jQuery.Callbacks('once memory')
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
}
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 // Flag to know if list is currently firing
firing,
// Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// First callback to fire (used internally by add and fireWith)
firingStart,
// Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
memory = options.memory && data;
fired = true;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
// 迭代list回调列表,列表中的回调被应用(或执行回调)
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
// 如果回调返回false,且options.stopOnFlase === true,则中断循环
// 注:data[1]是一个伪数组(self.fire方法中的arguments(参数集合))
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add // 阻止在add操作中可能执行的回调
break;
}
}
firing = false;
if ( list ) {
// (options.once === undefined),回调对象可以触发多次
if ( stack ) {
// 处理正在执行的回调中的fireWith操作
// 注:如果执行的回调中真的拥有fire或fireWith操作,那么列表中的回调将会无限循环的执行,请看实例1
if ( stack.length ) {
fire( stack.shift() );
}
}
// (options.once === true && options.memory === true)
// 回调列表致空,但允许add继续添加并执行回调
else if ( memory ) {
list = [];
}
// (options.once === true && options.memory === undefined)
// 禁用回调对象
else {
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" ) {
// 回调不唯一 或 唯一且不存在,则push
if ( !options.unique || !self.has( arg ) ) {
list.push( 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?
// 如果正在执行的回调执行了add操作,更新firingLength,将列表中新加进来的最后一个回调加入到回调执行的队列中
if ( firing ) {
firingLength = list.length; // With memory, if we're not firing then
// we should call right away
// 如果可能(options.memory === true),在回调对象不能再次fire(options.once === true)时,
// 我们应该使用memory(记录的最后一次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
// 在回调对象触发(fire)时,如果firingLength、firingIndex(正在执行的回调在列表list中的索引index)
// 大于等于 移除的回调的索引(index),分别减一,确保回调执行队列中未执行的回调依次执行
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
// 检查给定的回调是否在列表中
// 如果未给定回调参数,返回列表是否有回调
has: function( fn ) {
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
// Remove all callbacks from the list
// 将列表致空,list = []; firingLenght = 0;
empty: function() {
list = [];
firingLength = 0;
return this;
},
// Have the list do nothing anymore
// 禁用回调对象
// 将list赋值为undefined就可以使self中的add,remove,fire,fireWith方法停止工作
// 我认为这里把stack、memory赋值为undefined与否是没有任何关系的
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state
// 锁定回调列表
// 如果(fired !== true || options.memory === false),则视为禁用(disable)
// 如果(fired === true && options.memory === true),则视为options.once === true
// 请看实例2
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 ) {
// 回调对象未执行过fire 或且 可以执行多次(options.once === false)
// 如果(fired === true && options.once === true),则不会执行fire操作
if ( list && ( !fired || stack ) ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
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;
};

三、实例

实例1、 处理回调函数中的fire,或fireWidth操作

var cb = jQuery.Callbacks();

var fn1 = function(arg){ console.log( arg + '1' ); };
var fn2 = function(arg){ console.log( arg + '2' ); cb.fire(); };
var fn3 = function(arg){ console.log( arg + '3' ); }; cb.add(fn1, fn2, fn3); cb.fire('fn'); // 其中回调fn1,fn2,fn3无限制的循环调用 /*
控制台将无限制输出如下:
fn1
fn2
fn3
fn1
fn2
fn3
fn1
fn2
fn3
.
.
.
*/

实例2、 锁定(lock)操作各种场景中的用法

var cb1 = jQuery.Callbacks();
var cb2 = jQuery.Callbacks('memory');
var cb3 = jQuery.Callbacks('memory'); var fn1 = function(arg){ console.log( arg + '1' ); };
var fn2 = function(arg){ console.log( arg + '2' ); };
var fn3 = function(arg){ console.log( arg + '3' ); }; // 如果options.memory !== true,锁定操作视为禁用回调对象
cb1.add(fn1);
cb1.lock();
// 以下操作无任何反应
cb1.add(fn2);
cb1.fire('fn'); // 如果fried !== true,锁定操作也视为禁用回调对象
cb2.add(fn1);
cb2.lock();
// 以下操作无任何反应
cb2.add(fn2);
cb2.fire('fn'); // 如果(fired === true && options.memory === true),锁定操作类似控制标志once(options.once === true);
cb3.add(fn1);
cb3.fire('fn'); // fn1,此时fired === true
cb3.lock(); // 像是传入了'once'标志,jQuery.Callbacks('once memory');
cb3.add(fn2); // fn2
cb3.fire('fn'); // 再次触发,无任何反应
cb3.add(fn3); // fn3 // 再来看看jQuery.Deferred中的一段源码
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
]; // Handle state
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
} /*
当执行了tuples中前面两组中任意一个回调对象的fire方法时,后一组回调对象被锁定,
相当于(fired === true && options.memory === true),后一组回调对象实际为执行
jQuery.Callbacks('once memory')生成的回调对象。
*/

PS: 如有描述错误,请帮忙指正,如果你们有不明白的地方也可以发邮件给我,

  如需转载,请附上本文地址及出处:博客园华子yjh,谢谢!

jQuery回调、递延对象总结(上篇)—— jQuery.Callbacks的更多相关文章

  1. jQuery的deferred对象详解 jquery回调函数

    http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html jQuery的 ...

  2. jQuery回调、递延对象总结(一)jQuery.Callbacks详解

    前言: 作为参数传递给另一个函数执行的函数我们称为回调函数,那么该回调又是否是异步的呢,何谓异步,如:作为事件处理器,或作为参数传递给 (setTimeout,setInterval)这样的异步函数, ...

  3. jQuery - 01. jQuery特点、如何使用jQuery、jQuery入口函数、jQuery和DOM对象的区别、jQuery选择器、

    this指的是原生js的DOM对象 .css(“”):只写一个值是取值,写俩值是赋值 window.onload   ===   $(document).ready(); $(“”):获取元素   标 ...

  4. jQuery回调、递延对象总结(下篇) —— 解密jQuery.when方法

    前言: 前一篇文章中重点总结了一下then方法,它主要用来处理多个异步任务按顺序执行,即前一个任务处理完了,再继续下一个,以此类推: 而这一章节jQuery.when方法也是处理多个异步任务,它把多个 ...

  5. jQuery回调、递延对象总结(中篇) —— 神奇的then方法

    前言: 什么叫做递延对象,生成一个递延对象只需调用jQuery.Deferred函数,deferred这个单词译为延期,推迟,即延迟的意思,那么在jQuery中 又是如何表达延迟的呢,从递延对象中的t ...

  6. jQuery回调、递延对象总结

    jQuery回调.递延对象总结(上篇)—— jQuery.Callbacks jQuery回调.递延对象总结(中篇) —— 神奇的then方法 jQuery回调.递延对象总结(下篇) —— 解密jQu ...

  7. jQuery-1.9.1源码分析系列(六) 延时对象续——辅助函数jQuery.when

    $.when的说明 描述: 提供一种方法来执行一个或多个对象的回调函数,返回这些对象的延时(Deferred)对象. 说明(结合实例和源码): 如果你不传递任何参数,  jQuery.when()将返 ...

  8. jQuery的deferred对象学习

    #copy { background-color: lightgreen; padding: 15px; margin: 10px } 一.deferred对象简介 deferred对象是jquery ...

  9. js/jquery 回调函数的定义方法

    基本写法: 带参数的回调函数 以上回调函数,直接传入function作为参数,同样,还可以传入json对象作为参数...如下. 该方法的优势是可以定义多个回调函数....类似$.ajax回调函数中的s ...

随机推荐

  1. java的 clone方法

    1.java语言中没有明确提供指针的概念与用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如果取操作这个指针而已. 2.在java中处理基本数据类型时,都是 ...

  2. HDU2298 Toxophily

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...

  3. Jenkins实现测试环境到生产环境一键部署(Windows)

    前言: 因为dotnet在发布站点后,然后再上传服务时,会因为各种的网速问题,导致站点瞬间挂掉!那么通过一键部署,先在测试站点测试好的文件,复制到正式站点上的一个缓冲区,进行预热配置,之后再本机进行文 ...

  4. Android成长日记-Noification实现状态栏通知

    Notification可以作为状态栏的通知,实现这个效果需要使用NotificationManager实现控制类,才能实现对这个效果的显示 下面是实现状态栏显示效果的通知: 1. 首先在Layout ...

  5. Zabbix low-level discovery

    Version: zabbix 3.0.1 概述 Low-Level discovery 可以自动创建items,triggers,graphs为不同的实体对象. 例如:zabbix能自动监控服务器上 ...

  6. python实现自动输入命令回车操作

    苦逼的在sf上等了一天(问题链接),都没人来解答,只好自己想办法,东平西凑还是勉强实现了,记录一下: 安装完python2.7后,在cmd命令行输入python回车,后出现python相关的提示信息, ...

  7. Unity 依赖注入知识点

    三种依赖注入方法,构造器注入.属性注入.方法注入 可以配置Config文件,来实现不用修改代码.需要先将接口与实体关联,然后使用时会自动加载对应实体. namespace WeChatConsole ...

  8. BeautifulSoup高级应用 之 CSS selectors /CSS 选择器

    BeautifulSoup支持最常用的CSS selectors,这是将字符串转化为Tag对象或者BeautifulSoup自身的.select()方法. 本篇所使用的html为: html_doc ...

  9. oracle建表并设置ID为自动增长

    CREATE TABLESPACE shopping DATAFILE 'D:\oracle\mypc\oradata\orcl\shopping.dbf' SIZE 20M AUTOEXTEND O ...

  10. K米--案例分析

    第三次作业- -K米软件评测 第一部分 调研.评测 评测: 下载并使用.描述最简单直观的个人第一次上手体验: 第一次下载打开.这个简介粗矿的界面让偶着实吓了一跳.界面设计的有板有眼.直接了当.就像是在 ...