深入jQuery中的Callbacks()
引入
初看Callbacks函数很不起眼,但仔细一瞅,发现Callbacks函数是构建jQuery大厦的无比重要的一个基石。jQuery中几乎所有有关异步的操作都会用到Callbacks函数。
为什么搞了个Callbacks函数?
1 在 js 开发中,经常会遇到同步和异步这两个概念。
2 在javascript中神马是同步?神马是异步? 听我讲一个相亲的故事(本故事并不准确,仅供参考):
1 藤篮是一个漂亮姑娘,明年就要30岁了可现在还没有对象,于是,她的母亲给她报名了两家相亲机构,一家名叫同步相亲机构,另一家叫异步相亲机构。
2 同步相亲机构:这个机构的负责人很呆板,严格遵从“先来后到”的理念。
负责人首先给藤篮一个小册子,里面记录了很多的男士资料,让藤篮从里面找一个心仪的男士,然后安排藤篮与他见面。
藤兰很快选中了令狐冲。负责人告诉藤篮:“令狐冲明天和任女士有约,你们只能后天见面了“。藤篮说:“好的,我正好准备准备”。
结果两天过后,负责人告诉藤篮:因为昨天任女士有事,没能和令狐冲见面,所以我们安排今天任女士与令狐冲见面,你到明天再约吧!
藤篮很生气:既然昨天任女士有事,为啥不安排让我昨天和令狐冲见面呢?
负责人说:不行!俺们讲究的是先来后到! 因为任女士先约的令狐冲,甭管怎么着,你都得排在任女士后面
藤篮很生气! 于是来到了异步相亲机构。
3 异步相亲机构:这个机构的负责人则很灵活:
一般情况下遵从先来后到的理念,特殊情况特殊对待。
藤篮很喜欢这个负责人的理念,于是和这里的负责人携手一生.......
4 再来总结一下:Javascript语言的执行环境是"单线程",所谓"单线程",就是指一次只能完成一件任务。
同步就是: 后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的
异步就是:一个任务可能有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而执行回调函数,
后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的
3 上面异步中讲到,一个任务可能有一个或多个回调函数,假如a任务有a1,a2,a3这三个回调函数,b也需要a1,a2,a3这三个回调函数。我们需要这样做:
function a1(){ }
function a2(){ }
function a3(){ }
var a = setTimeout(function(){
a1();
a2();
a3();
},1000);
var b = setTimeout(function(){
a1();
a2();
a3();
},2000)
4 上面的代码很麻烦是不是?Callback就是来解决这种麻烦!!
Callbacks如何解决这种麻烦?
先来说一下大体思路:
1 首先我们每次调用Callbacks(),都会返回一个callbacks对象,这个对象有一个仅仅只有自己才能访问的数组(就是用了闭包呗)
2 这个数组就是用来存储 回调函数的。
3 callbacks对象有许多方法,可以对数组进行操作,比如说:添加(add),清空(empty),删除(remove),运行数组中的所有回调函数(fire).......
4 那么,我们上面---为什么搞了个Callbacks函数?---的第三条 任务a,任务b可以如下改写:
function a1(){ }
function a2(){ }
function a3(){ }
var allCallback = $.Callbacks();
allCallback.add(a1,a2,a3); var a = setTimeout(function(){
allCallback.fire()
},1000);
var b = setTimeout(function(){
allCallback.fire()
},2000)
升华:
Callbacks可不仅仅只实现了这些,他还提供了很多参数: var a = Callbacks('once memory unique stopOnFalse ')
once: 如果创建Callbacks时加入该参数,则运行数组中的所有回调函数之后,也就是fire()之后,会清空数组。
memory: 会保存上一次运行fire(args)时的参数args,每当添加一个新的回调函数到数组中,会立即使用args作为参数调用新加的函数一次
unique: 确保数组中的回调函数互不相同
stopOnFalse: 当运行fire()时,若某个回调函数返回false,则立即终止余下的回调函数执行
尽管这些参数可能有些初看没啥用。 但是不得要说,jQuery的开发人员真的很细致!
源码讲解:
define([
"./core",
"./var/rnotwhite"
], function( jQuery, rnotwhite ) { // String to Object options format cache
var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache
/*
如果: var a = $.Callback('once memory')
则 optionsCache中会有这么一项:"once memory":{memory:true,once:true}
*/
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( rnotwhite ) || [], 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)
memory,
// Flag to know if list was already fired list是否已经被fire函数调用过
fired,
// Flag to know if list is currently firing 当前是否正在调用fire函数
firing,
// First callback to fire (used internally by add and fireWith) 第一个被执行的回调函数在list的位置
firingStart,
// End of the loop when firing fire函数要运行的回调函数的个数
firingLength,
// Index of currently firing callback (modified by remove if needed) 当前正在执行的回调函数的索引
firingIndex,
//回调函数数组
list = [],
// Stack of fire calls for repeatable lists 可重复的回调函数栈。我们可能会短时间内执行多次fire(),若当前的fire()正在迭代执行回调函数,而紧接着又执行了一次fire()时,会将下一次的fire()参数等保存至stack中,等待当前的fire()执行完成后,将stack中的fire()进行执行
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
// data[0] 是一个对象,data[1]则是回调函数的参数
memory = options.memory && data; // 很精妙,仔细体会一下这句代码,如果调用Calbacks时传入了memory,则memory = data,否则memory = false
fired = true; // 在调用本函数时,将fired状态进行修改
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true; // 迭代回调函数之前,将firing状态进行修改
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false &&
options.stopOnFalse ) { // 运行回调函数的同时,检测回调函数是否返回false,若返回false,且调用Callbacks时传入stopOnFalse参数,则终止迭代 memory = false; // To prevent further calls using add 既然终止迭代了,那么之后添加的回调函数都不应该被调用,将memory设置为false
break;
}
}
firing = false; // 迭代回调函数完成后,将firing状态进行修改
if ( list ) {
if ( stack ) { // 没有使用once参数
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) { // 使用了once memory参数,则在迭代完回调函数之后清空list
list = [];
} else { // 其他
self.disable();
}
}
},
// Actual Callbacks object
self = {
// 将一个新的回调函数添加至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" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg ); // 若参数中的元素为函数且(无unique参数或者list中没有该函数),则将该函数添加至list末尾
}
} else if ( arg && arg.length && type !== "string" ) { // arg的长度不为0且每项的类型不为字符串,也就是args为这种情况:[[fun1,fun2...],[fun3,fun4]](不仅限于这种情况)
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
// 当Callback中的firingLength变为 动态的! 也就是:只要我们向list中添加了一个新的回调函数,即使在fire()运行过程中,改变也能立即体现出来
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) { // 如果当前没有执行回调函数,且存在memory参数,则执行新添加的回调函数
firingStart = start;
fire( memory );
}
}
return this;
},
// Remove a callback from the list 将一个回调函数从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
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 清空数组
empty: function() {
list = [];
firingLength = 0;
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
// 使用传入的context作为当前函数的执行上下文
fireWith: function( context, args ) {
if ( list && ( !fired || stack ) ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
if ( firing ) {
stack.push( args ); // 如果当前正在迭代执行回调函数,则将新的fire参数推入stack中
} 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
// 用来确定当前callback对象是否被fire()过
fired: function() {
return !!fired;
}
}; return self;
}; return jQuery;
});
深入jQuery中的Callbacks()的更多相关文章
- jquery中的callbacks之我见
callbacks是jquery的核心之一. 语法如下: jQuery.Callbacks( flags ) flags 类型: String 一个用空格标记分隔的标志可选列表,用来改变回调列表中 ...
- 深入理解jQuery中的Deferred
引入 1 在开发的过程中,我们经常遇到某些耗时很长的javascript操作,并且伴随着大量的异步. 2 比如我们有一个ajax的操作,这个ajax从发出请求到接收响应需要5秒,在这5秒内我们可以 ...
- 读jQuery源码 - Callbacks
代码的本质突出顺序.有序这一概念,尤其在javascript——毕竟javascript是单线程引擎. javascript拥有函数式编程的特性,而又因为javascript单线程引擎,我们的函数总是 ...
- JQuery中的回调对象
JQuery中的回调对象 回调对象(Callbacks object)模块是JQuery中的一个很基础的模块,很多其他的模块(比如Deferred.Ajax等)都依赖于Callbacks模块的实现.本 ...
- js便签笔记(6)——jQuery中的ready()事件为何需要那么多代码?
前言: ready()事件的应用,是大家再熟悉不过的了,学jQuery的第一步,最最常见的代码: jQuery(document).ready(function () { }); jQuery(fun ...
- jQuery源码分析-jQuery中的循环技巧
作者:nuysoft/JS攻城师/高云 QQ:47214707 EMail:nuysoft@gmail.com 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. 前记:本文收集了jQuery ...
- Jquery中.ajax和.post详解
之前写过一篇<.NET MVC 异步提交和返回参数> ,里面有一些ajax的内容,但是不深入,这次详细剖析下jquery中$.ajax的用法. 首先,上代码: jquery-1.5.1 $ ...
- jquery中的$.ajax()的源码分析
针对获取到location.href的兼容代码: try { ajaxLocation = location.href; } catch( e ) { // Use the href attribut ...
- [转载]Jquery中$.get(),$.post(),$.ajax(),$.getJSON()的用法总结
本文对Jquery中$.get(),$.post(),$.ajax(),$.getJSON()的用法进行了详细的总结,需要的朋友可以参考下,希望对大家有所帮助. 详细解读Jquery各Ajax函数: ...
随机推荐
- linux shell:nginx日志切割脚本
需求原因:nginx不具备日志切割功能,日志量较大,方便分析. 实现目的:完成nginx日志切割,并根据时间命名 简要命令: mv /usr/local/tengine/logs/access.l ...
- DSP中.gel文件的作用
GEL是CCS提供的一种解释语言,使用该语言写出的GEL,函数具有两在功能,一是配置CCS工作环境,二是直接访问目标处理器DSP(包括DSP软/硬仿真器).用户可以使用GEL函数完成类似于宏操作的自动 ...
- Go语言学习笔记1 变量,类型以及赋值
1.变量 1.1 声明变量 使用var关键字可以创建一个指定类型的变量: var i int = 0 var i = 0 var i int 以上三个表达式均是合法的,第三个表达式会将i初始化为int ...
- 移动端 isScroll自定义实现
var scroll_flag=null;var goodNum = 11;var i_c = 0;function loadInsuranceList(){ //这里写滚动出来 加载的数据$.aja ...
- Calendar
/* * Calendar:它为特定瞬间与一组诸如 YEAR.MONTH.DAY_OF_MONTH.HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方 ...
- libZPlay 音频编码解码器库
libZPlay 音频编码解码器库 http://www.oschina.net/p/libzplay libZPlay 播放音乐并显示 FFT 图形 :http://www.oschina.net/ ...
- 118、通过solid来定义不同边框的颜色,可以只定义一个边框的颜色
以下是设置按钮的右边框和底边框颜色为红色,边框大小为3dp,如下图: 在drawable新建一个 buttonstyle.xml的文件,内容如下: <?xml version="1.0 ...
- 可提高工作效率的 PL/SQL Developer 设置
1.将Window List 列表展示出来并保存当前布局 ①Tools-->Windows List (展示窗口列表) ②Window-->Save Layout (保存当前布局) 2.设 ...
- 从jquery源码中看类型判断和数组的一些操作
在深入看jquery源码中,大家会发现源码写的相当巧妙.那我今天也通过几个源码中用到的技巧来抛砖引玉,希望大家能共同研究源码之精华,不要囫囵吞枣. 1.将类数组转化成数组 我想大家首先想到的方法是fo ...
- Linux学习笔记之——基础命令学习
1.find 按照名字查找:find / -name file_name 2.zip压缩 1) 我想把一个文件repartition.txt和一个目录invader压缩成为amateur.zip: ...