jquery源码解析:jQuery工具方法when详解
我们先来看when方法是如何使用的:
var cb = $.when(); //when方法也是返回一个延迟对象,源码是return deferred.promise();返回的延迟对象不能修改状态
$.Deferred()也是返回一个延迟对象,那么它们的区别是什么呢?$.Deferred()只能针对一个延迟对象做成功,失败,进行中的操作,而$.when()可以针对多个延迟对象做以上操作。举个例子:
function a(){
var cb = $.Deferred();
cb.resolve();
return cb;
}
function b(){
var cb = $.Deferred();
cb.resolve();
return cb;
}
a().done(function(){alert("成功")});
以上代码,我的需求是等a中的延迟对象触发,以及b中的延迟对象触发,后,再弹出成功。因为这时需要处理两个延迟对象,所以通过 $.Deferred()无法实现。这时就需要用$.when()了。我们把最后面那句代码改成:
$.when(a(),b()).done(function(){alert("成功")});就可以实现上面的需求了。这句代码的意思就是,等a,b中的延迟对象都触发了resolve后,才会执行里面回调方法,弹出成功。
还有一个用法:
$.when(a(),b()).done(function(){alert("成功")}).fail(function(){alert("失败")}),这段代码的意思,只要a或b中的任何一个延迟对象触发reject方法,就会弹出失败。
大家看到when方法里面传的都是延迟对象,而且这个延迟都是具有状态的(调用resolve是成功状态,调用reject是失败状态)。如果传入的不是延迟对象,比如:$.when("chaojidan",b()).done(function(){alert("成功")}).fail(function(){alert("失败")}),那么when方法会忽略chaojidan这个字符串,只会针对b里面的延迟对象进行处理。如果都是字符串,那么将都忽略,直接执行done添加的方法。而且done添加的回调方法中,可以接收到这些字符串(arguments[0]是第一个字符串,arguments[1]是第二字符串,以此类推)。
接下来进行源码解析:
when: function( subordinate , ... , subordinateN ) {
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length,
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
deferred = remaining === 1 ? subordinate : jQuery.Deferred(), //如果传入一个延迟对象,when方法返回的就是这个延迟对象。
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) { //如果触发的是resolve,remaining就会减1,直到为0,就证明when里面的所有延迟对象都执行了resolve,这时,就会执行when返回的延迟对象deferred的resolveWith,而这将会触发deferred通过done添加的方法。
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
if ( length > 1 ) { //传入2个以及2个以上的参数时,进入if语句
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise() //如果是延迟对象,就添加三种状态要执行的方法。
.done( updateFunc( i, resolveContexts, resolveValues ) ) //如果一个延迟对象触发resolve,就执行updateFunc方法返回的函数,而这个函数会判断所有的延迟对象都触发resolve了没有,如果都触发了,那么就执行when返回的延迟对象的resolve。如果不是全部,那么不做处理
.fail( deferred.reject ) //只要有一个延迟对象触发reject,就会执行when返回的延迟对象的reject。
.progress( updateFunc( i, progressContexts, progressValues ) );
} else { //传入的如果不是延迟对象,就直接减一。
--remaining;
}
}
}
if ( !remaining ) { //当不传参数的时候(或传一个字符串的时候),直接执行这里
deferred.resolveWith( resolveContexts, resolveValues );
}
return deferred.promise();
}
when方法最难的莫过于updateFunc方法,这个方法会返回一个函数,而这个函数会调用updateFunc方法中的参数,形成一个闭包。我们来分析第一个updateFunc方法,updateFunc( i, resolveContexts, resolveValues ),这里假设传进when方法的是a,b两个延迟对象。当第一次循环时,done中的updateFunc方法,i=0,resolveContexts = [undefined , undefined ],resolveValues =[a,b],执行updateFunc方法返回一个function。progress中的updateFunc方法,i=0,progressContexts = [undefined , undefined ],progressValues =[undefined , undefined ],执行updateFunc方法返回一个function。第二次循环时,只是i=1,其他都是一样的。之后,我们触发a的resolve方法,这时就会执行i=0的这个function。
function( value ) { //如果resolve没有传参数,那么value为undefined。其中函数中的contexts=resolveContexts,values = resolveValues
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; //如果没有传入参数,那么arguments.length=0,resolveValues[0] = undefined。resolveValues=[undefined , b],延迟对象a就变成undefined
if( values === progressValues ) { //[undefined , b]不是[undefined , undefined ],就把remaining减一,刚开始remaining是2,因为传入了两个延迟对象a,b。
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) { //减1后,还是1,所以不执行下面的方法。
deferred.resolveWith( contexts, values );
}
}
当b执行resolve方法时,也会执行上面的function,这时i=1,所以resolveValues[1] =undefined,resolveValues=[a, undefined ],它不等于[undefined , undefined ],所以执行--remaining,这时remaining变成0了,就会执行deferred.resolveWith( contexts, values );方法了。因为这时a,b两个延迟对象都执行了resolve了。
上面讲的都是执行延迟对象a,b的resolve方法。我们再来讲一下执行a,b的notify方法。
我们首先执行a的notify方法,这时会执行i=0的function
function( value ) { //如果notify没有传参数,那么value为undefined。其中函数中的contexts=progressContexts ,values = progressValues
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; //如果没有传入参数,那么arguments.length=0,progressValues[0] = undefined。progressValues =[undefined , undefined]
if( values === progressValues ) { //[undefined , undefined ]还是[undefined , undefined ],就进入if语句,触发when返回的延迟对象deferred的notify方法,当然会触发deferred的progress添加的函数。
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
}
也就是不传参(value等于undefined)的情况下,触发其中一个(a或b)notify,也会触发when返回的延迟对象的notify。
那如果传参的话,function执行的流程就不是上面的流程了,传参还分一个还是多个这两种情况。按照我上面的执行流程,很容易理解。说实话,没必要,知道大概的执行流程就行了,最重要的知道怎么用就行了。
加油!
jquery源码解析:jQuery工具方法when详解的更多相关文章
- jQuery 源码分析(十八) ready事件详解
ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加 ...
- jQuery 源码分析(十一) 队列模块 Queue详解
队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...
- gulp源码解析(一)—— Stream详解
作为前端,我们常常会和 Stream 有着频繁的接触.比如使用 gulp 对项目进行构建的时候,我们会使用 gulp.src 接口将匹配到的文件转为 stream(流)的形式,再通过 .pipe() ...
- jquery源码解析:jQuery延迟对象Deferred(工具方法)详解1
请先看上一课的回调对象.Deferred是通过extend添加到jQuery中的工具方法.如下所示: jQuery.extend({ Deferred: function( func ) { }, w ...
- jquery源码解析:jQuery延迟对象Deferred(工具方法)详解2
请接着上一课继续看. $.Deferred()方法中,有两个对象,一个是deferred对象,一个是promise对象. promise对象有以下几个方法:state,always,then,prom ...
- jQuery源码分析_工具方法(学习笔记)
expando:生成唯一JQ字符串(内部使用) noConflict():防止冲突 isReady:DOM是否加载完成(内部) readyWait:等待多少文件的计数器(内部) holdReady() ...
- jQuery 源码解析(三) pushStack方法 详解
该函数用于创建一个新的jQuery对象,然后将一个DOM元素集合加入到jQuery栈中,最后返回该jQuery对象,有三个参数,如下: elems Array类型 将要压入 jQuery 栈的数组元素 ...
- Spring源码解析--IOC根容器Beanfactory详解
BeanFactory和FactoryBean的联系和区别 BeanFactory是整个Spring容器的根容器,里面描述了在所有的子类或子接口当中对容器的处理原则和职责,包括生命周期的一些约定. F ...
- Linux源码解析-内核栈与thread_info结构详解
1.什么是进程的内核栈? 在内核态(比如应用进程执行系统调用)时,进程运行需要自己的堆栈信息(不是原用户空间中的栈),而是使用内核空间中的栈,这个栈就是进程的内核栈 2.进程的内核栈在计算机中是如何描 ...
- 十七.jQuery源码解析之入口方法Sizzle(1)
函数Sizzle(selector,context,results,seed)用于查找与选择器表达式selector匹配的元素集合.该函数是选择器引擎的入口. 函数Sizzle执行的6个关键步骤如下: ...
随机推荐
- 两个不同vim之间复制内容
常规想法是打开两个vim,然后进行yy和p操作,但是实践证明根本是不行的.此时,我们需要分割窗口,然后就可以复制粘贴了.步骤如下: 假设我要把srv.c文件的readline函数整体复制到cli.c文 ...
- css实现图标移上图标弹跳效果
html部分: <div class="bounce" style="width:20px;height:20px;border:1px solid red;&qu ...
- opennebula 添加kvm主机日志
Sun Sep :: [ReM][D]: Req: UID: HostDelete invoked, Sun Sep :: [ReM][D]: Req: UID: HostDelete result ...
- 一张图记住TCP/IP通讯中的IP地址配置
TCP/IP通讯情景: 用网线将计算机A(服务器Server)和计算机B(Client)连接起来.程序代码在计算机A中,计算机B中安装有TCP/IP通讯助手. (图中屏幕大的是计算机A,屏幕小的笔记本 ...
- paho_c_pub 使用方法
Latest Paho Status (2) 摘自:http://modelbasedtesting.co.uk/ I last wrote about the state of Paho in Oc ...
- Linux 设置默认编辑器(以nano为例)
查看nano地址 which nano output: /usr/bin/nano 设置默认编辑器 nano ~/.bashrc export EDITOR=nano alias vi=/usr/bi ...
- Charles 4.2.5 破解原理.RP
请支持正版,仅供技术交流. 写了个小工具方便在线处理,在线破解 https://github.com/8enet/Charles-Crack 2018/4/8 Charles 4.2.5 替换类名和方 ...
- 实践作业4---DAY3阶段二。
第二阶段是用户调研,我们小组选择了一个5班的同学,作为采访对象.采访比较详实,但是样本太少,不太有说服力. 具体采访详情如下: 问:请问您使用喜欢发表Blog么? 答:肯定有啊. 问:都用什么网站发表 ...
- java中super的用法
在Java中,super关键字有2个用法,一个是访问父类的函数,一个是访问父类的变量,总体来说,就是一个功能,访问父类的成员. 代码如下: class Person { String name ; i ...
- Activator不能创建包含范型参数的实例
Activator包含特定的方法,用以在本地或从远程创建对象类型,或获取对现有远程对象的引用.此类不能被继承. 具体介绍:http://msdn.microsoft.com/zh-cn/library ...