第三十三课:jQuery Deferred详解1
之前我们讲了Mochikit Deferred,JSDeferred,现在讲jQuery Deferred。首先,我们先来讲下他们的区别:
在保存回调函数时,Mochikit Deferred(dojo Deferred)是用一个2维数组保存的,里面的小数组只有两项,一个是成功回调的函数,一个是失败回调的函数。
JSDeferred则每个实例都必有ng,ok这两个回调函数。
jQuery Deferred则一个_Deferred负责添加成功回调,一个负责添加错误回调。
它们的API区别如下图:
接下来,我们来看一下jQuery1.51的源码:
此版本jQuery的Deferred对象的实现,是使用了一个双链结构,每条链都由_Deferred方法返回的deferred对象实现。
var promiseMethods = "then done fail isResolved isRejected promise".split(" "),
sliceDeferred = [].slice;
jQuery.extend({
_Deferred: function(){ //jQuery的静态方法,在Deferred中被调用,请先看Deferred方法。
var callbacks = [], fired, firing,cancelled,
deferred = { //调用jQuery._Deferred()方法,返回的对象
done:function(){ //根据下面的例子:var cb = $.Deferred();done方法中传入的参数是failDeferred.cancel,其实就是deferred.cancel。
if(!cancelled){ //如果没有被取消,就进入if语句,刚开始是undefined,取非,所以是true,进入if语句。
var args = arguments, i , length, elem, type, _fired;
if(fired){ //刚开始是undefined,不进入if语句。
_fired = fired;
fired = 0;
}
for(i=0,length = args.length ;i<length; i++){ //把传入done方法的参数遍历,这里就是failDeferred.cancel
elem = args[i];
type = jQuery.type(elem); //判断每一个参数的类型
if(type === "array"){ //如果是数组,就重新调用deferred.done方法。
deferred.done.apply(deferred,elem);
}else if(type === "function"){ //如果参数的类型是函数,就把函数添加到callbacks数组中。这里的failDeferred.cancel是函数,所以执行这里。
callbacks.push(elem); //这时callbacks = [failDeferred.cancel]
}
}
if(_fired){ //undefined,不进入if语句。
deferred.resolveWith(_fired[0], _fired[1]);
}
}
return this; //this就是调用done方法的对象
},
resolveWith:function(context ,args){
if(!cancelled && !fired && !firing){ //根据例子来讲解的,如果没取消,也没触发,也没在触发中,那么就进入if语句进行触发。
args = args || [];
firing = 1; //改变状态,使这个Deferred处于触发中状态
try{
while(callbacks[0]){ //callbacks = [failDeferred.cancel , fun1]
callbacks.shift().apply(context, args); //shift方法,把数组的第一项删除,并返回第一项。这里执行failDeferred.cancel方法。因为调用了apply方法,其实就是在cb的执行上下文中执行failDeferred.cancel方法。但是由于在cancel方法中,不是针对this操作,而是针对私有变量callbacks操作,因此执行failDeferred.cancel方法时操作的还是failDeferred对象中的callbacks。所以这时,failDeferred对象中的私有变量callbacks = [deferred(局部)的cancel]会变成[]。这意味着cb延迟对象触发了resolve方法(成功触发)。这时cb的callbacks = [ fun1],然后继续循环,取出fun1,执行。然后cb和failDeferred的callbacks数组都成为[]。
}
}finally{
fired = [context, args];
firing = 0;
}
}
return this;
},
resolve:function(){
deferred.resolveWith(this, arguments); //resolve方法其实就是执行resolveWith方法,这里的deferred其实就是cb。因为执行var cb = $.Deferred()时,cb引用了deferred对象,callbacks等属性。所以这些变量和属性都会保存在内存中,因此你调用cb.resolve时,执行的deferred的方法,其实就是cb的resolveWith方法。而里面的callbacks属性就是cb对象的callbacks属性。
return this;
},
isResolved:function(){
return !!(firing || fired);
},
cancel:function(){
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
},
Deferred:function(func){ //也是jQuery的静态方法,它就是jQuery的Deferred。里面有两个jQuery._Deferred()方法返回的deferred对象
var deferred = jQuery._Deferred(), //执行jQuery的静态方法,返回一个deferred对象。因为这里的局部变量deferred跟jQuery._Deferred()返回的对象deferred重名,因此我们叫局部变量deferred称为deferred(变量)
failDeferred = jQuery._Deferred(), //同样返回一个deferred对象。跟上面那个deferred对象不是同一个,但是具有相同的方法和属性。
promise;
jQuery.extend(deferred,{ //扩展deferred变量引用的deferred对象,而不是failDeferred引用的deferred对象。deferred对象默认有done,resolveWith,resolve,isResolved,cancel五个方法。deferred(变量)对象扩展后,又增加了then,fail,rejectWith,reject,isRejected,promise等六个方法。
then : function(doneCallbacks , failCallbacks){
deferred.done(doneCallbacks).fail(failCallbacks);
return this;
},
fail:failDeferred.done, //当deferred(变量)对象调用fail方法添加失败回调函数时,其实调用的是代表失败的failDeferred对象done方法。
rejectWith:failDeferred.resolveWith,
reject:failDeferred.resolve, //当deferred(变量)对象触发失败回调方法时,也就是触发fail方法添加的回调函数时,就会调用代表失败的failDeferred对象的resolve方法,而这个方法会执行failDeferred对象中通过done方法添加的回调函数。这样就实现了Deferred对象的成功回调和失败回调的操作(内部通过两个链,一个链代表成功回调,一个链代表失败回调)。也就是说,失败回调交给failDeferred对象处理,成功回调交给deferred(变量)对象自己处理,这个deferred(变量)对象就是$.Deferred()返回的对象。
isRejected:failDeferred.isResolved,
promise:function(obj){ //这是一个单例方法,无论调用多少次这个promise方法,都只会返回同一个对象。
if(obj == null){
if(promise){
return promise;
}
promise = obj = {};
}
var i = promiseMethods.length;
while(i--){
obj[promiseMethods[i]] = deferred[promiseMethods[i]]; //Promise对象有then,done,fail,isResolved,isRejected,promise六个方法,它没有触发回调的方法,因此Promise对象只能添加回调方法,不能触发回调方法的执行。
}
return obj;
}
});
deferred.done(failDeferred.cancel).fail(deferred.cancel); //调用deferred(变量)的done方法和fail方法。我们去看一下这两个方法,请去看_Deferred()。当执行完done方法后,deferred(变量)对象中的私有变量callbacks = [failDeferred.cancel],返回deferred(变量),再调用它的fail方法。它的fail方法是failDeferred.done方法。而failDeferred.done方法就是deferred对象的done方法,传入的参数是deferred(变量)cancel方法。执行完deferred(变量)的fail方法后,deferred(变量)对象中的私有变量callbacks = [failDeferred.cancel]。而failDeferred对象中的私有变量callbacks = [deferred(局部)的cancel]。
delete deferred.cancel; //删除deferred(局部)的cancel方法。
if(func){
func.call(deferred,deferred); //举个例子:如果在var cb = $.Deferred();中传入了一个函数func,那么就会在deferred(局部)对象的执行上下文中执行这个函数,并且把此对象作为参数传给函数func。
}
return deferred; //返回deferred(局部)对象。
},
when:function(firstParam){
var args = arguments, i =0, length = args.length, count = length;
var deferred = length <=1 && firstParam && jQuery.isFunction(firstParam.promise) ? firstParam : jQuery.Deferred(); //如果只传入了一个Deferred或Promise对象。,就直接使用此对象,否则生成一个新的Deferred对象。
function resolveFunc(i){ //定义私有函数
return function(value){
args[i] = arguments.length > 1 ? sliceDeferred.call(arguments, 0 ) : value; //sliceDeferred就是[].slice方法。args[0]是cb1.resolve()传入的参数值,args[1]是cb2.resolve()传入的参数值。
if(!(--count)){ //如果传入when中的所有Deferred都触发了resolve方法,这里的count才会变成0,因此为真,触发cb3的resolve方法。
deferred.resolveWith(deferred,sliceDeferred.call(args,0));
}
}
}
if(length > 1){ //如果传入了2个以及2个以上的Deferred对象
for(; i < length ;i++){
if(args[i] && jQuery.isFunction(args[i].promise)){ //如果传入的参数是Deferred对象
args[i].promise().then(resolveFunc(i), deferred.reject); //取到Deferred的Promise对象,调用它的then方法。then方法是:function(doneCallbacks , failCallbacks){ deferred.done(doneCallbacks).fail(failCallbacks); return this; },then方法可以同时添加成功回调和失败回调。这里举个例子:var cb1 = $.Deferred(); var cb2 = $.Deferred(); var cb3 = $.when(cb1 , cb2).done( function fun3(){ cb1和cb2都触发成功了 } ); cb1的成功回调数组callbacks=[resolveFunc[0]],失败的回调数组callbacks=[cb3.reject],cb2的成功回调数组callbacks=[resolveFunc[1]],失败的回调数组callbacks=[cb3.reject],cb3的成功回调数组为callbacks = [fun3]。这时,如果你触发cb1和cb2的resolve方法,那么就会触发cb3的resolve方法。如果你只触发了cb1或cb2中的其中一个resolve方法,那么cb3的resolve方法不会触发,需要等cb1和cb2的resolve方法都触发了,才会触发。如果你触发了cb1或cb2中任何一个reject方法,那么cb3的reject方法就会触发。
}else{
--count; //如果传入的都不是Deferred对象,这里的count就会减为0.如果有一个,就不会等于0.
}
}
if(!count){ //如果为0,就会执行这个方法。举个例子:$.when("chaojidan","chaojidan1","chaojidan2")
deferred.resolveWith(deferred,args);
}
}else if(deferred !== firstParam){ //如果没传入Deferred对象,或者传入了一个不是Deferred对象的参数。举个例子:var cb = $.when()或$.when("chaojidan")
deferred.resolveWith(deferred,length?[firstParam] : [] );
}
return deferred.promise(); //如果传入了一个Deferred对象,就直接返回它的Promise对象。举个例子:var cb = $.Deferred();var cb1 = $.when(cb),这时的cb1其实是cb的Promise对象。其他情况,都返回一个新的Deferred的Promise对象。
}
})
此段代码,我们根据这个例子:var cb = $.Deferred();cb.done(function fun1(){alert(1)}),cb.resolve();来讲解。从上可知,当调用了var cb = $.Deferred();方法后,cb的callbacks=[failDeferred.cancel],failDeferred对象中的私有变量callbacks = [cb.cancel],当调用cb.done(function fun1(){alert(1)})方法之后,cb的callbacks=[failDeferred.cancel , fun1],failDeferred不变,当调用cb.resolve();方法后,其实就是调用deferred.resolveWith(cb, []);请看此方法解释。最终,cb和failDeferred的私有变量callbacks都会变成[]。
这是jQuery的第一代的Deferred模块,在jQuery1.6,1.7,1.8中,都进行优化,下节课讲解。
加油!
第三十三课:jQuery Deferred详解1的更多相关文章
- 第三十四课:jQuery Deferred详解2
上一课主要分析了jQuery1.51版本的jQuery Deferred.在jQuery1.6中,jQuery Deferred添加了两个方法,always,pipe. always用来添加回调,无论 ...
- 第三十课:JSDeferred详解1
本课难度非常大,看一遍,蛋会疼,第二遍蛋不舒服,第三遍应该貌似懂了.初学者莫来,没必要,这完全就是一个研究. JSDeferred是日本高手cho45搞出来的,其易用性远胜于Mochikit Defe ...
- 第三十一课:JSDeferred详解2
这一课,我们先接着上一课讲一下wait方法,以及wait方法是如何从静态方法变化实例方法的. 首先我们先看wait方法为啥可以从静态方法变成实例方法,请看register源码: Deferred.re ...
- Jquery Deferred 详解
近期由于公司项目主要由我来负责前端,所以打算优化一下代码.在jquery 里面有个Deferred的对象.为了研究这个也看了不少资料,其中阮一峰的博客写的很详细,这里转载一下. 一.什么是deferr ...
- jQuery:详解jQuery中的事件(二)
上一篇讲到jQuery中的事件,深入学习了加载DOM和事件绑定的相关知识,这篇主要深入讨论jQuery事件中的合成事件.事件冒泡和事件移除等内容. 接上篇jQuery:详解jQuery中的事件(一) ...
- jQuery.validator 详解二
前言:上一篇详细的介绍了jQuery.validator( 版本v1.13.0 )的验证规则,这一篇重点讲述它的源码结构,及如何来对元素进行验证,错误消息提示的内部实现 一.插件结构(组织方式) 在讲 ...
- jQuery.validator 详解
jQuery.validator 详解二 前言:上一篇详细的介绍了jQuery.validator( 版本v1.13.0 )的验证规则,这一篇重点讲述它的源码结构,及如何来对元素进行验证,错误消息提示 ...
- Jquery 选择器 详解 js 判断字符串是否包含另外一个字符串
Jquery 选择器 详解 在线文档地址:http://tool.oschina.net/apidocs/apidoc?api=jquery 各种在线工具地址:http://www.ostools ...
- NeHe OpenGL教程 第三十三课:TGA文件
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
随机推荐
- java Timer(定时调用、实现固定时间执行)
最近需要用到定时调用的功能.可以通过java的Timer类来进行定时调用,下面是有关Timer的一些相关知识. 其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个 ...
- pushd
# MAN 手册原文: pushd [-n] [+n] [-n] pushd [-n] [dir] Adds a directory to ...
- zookeeper适用场景:配置文件同步
问题导读:1.本文三个角色之间是什么关系?2.三个角色的作用是什么?3.如何代码实现这三个角色的作用? 在 zookeeper适用场景:zookeeper解决了哪些问题有关于分布式集群配置文件同步问题 ...
- 边工作边刷题:70天一遍leetcode: day 78
Graph Valid Tree 要点:本身题不难,关键是这题涉及几道关联题目,要清楚之间的差别和关联才能解类似题:isTree就比isCycle多了检查连通性,所以这一系列题从结构上分以下三部分 g ...
- Codeforces Round #267 Div.2 D Fedor and Essay -- 强连通 DFS
题意:给一篇文章,再给一些单词替换关系a b,表示单词a可被b替换,可多次替换,问最后把这篇文章替换后(或不替换)能达到的最小的'r'的个数是多少,如果'r'的个数相等,那么尽量是文章最短. 解法:易 ...
- 【转】Python中string的strip,lstrip,rstrip用法
Python中的strip用于去除字符串的首尾字符串,同理,lstrip用于去除左边的字符,rstrip用于去除右边的字符. 这三个函数都可传入一个参数,指定要去除的首尾字符. 需要注意的是,传入的是 ...
- nginx 一二事(2) - 创建虚拟静态服务器
一.什么是nginx 是一个C语言开发的HTTP反向代理服务器,性能非常高 一个俄罗斯的哥们开发的,官方提供的测试性能能够达到5W的并发,我的天呐~,实际测试差不多是2W,而淘宝的牛人可以优化到200 ...
- AC日记——潜伏者 洛谷 P1071 (模拟)
题目描述 R 国和 S 国正陷入战火之中,双方都互派间谍,潜入对方内部,伺机行动.历尽艰险后,潜伏于 S 国的 R 国间谍小 C 终于摸清了 S 国军用密码的编码规则: 1. S 国军方内部欲发送的原 ...
- emberjs重写补充类之reopen方法和reopenClass方法
无需一次性将类定义完全,你可以使用reopen方法来重新打开(reopen)一个类并为其定义新的属性. Person.reopen({ isPerson: true }); Person.create ...
- jenkins配置记录(2)--代码发布流程
在我们的日常运维工作中,使用jenkins来完成业务代码发版上线是至关重要的一环.前面已经提到在jenkins上添加用户权限的操作,今天重点说下如何在jenkins下构建项目工程进行代码发布? 在此简 ...