jQuery源码阅读(十三)---jQuery异步队列模块
来源:互联网 发布:淘宝推广佣金 编辑:程序博客网 时间:2024/05/18 03:28
上一篇博客分析了Callbacks实现原理,而在jQuery中,Ready函数的实现,异步队列以及队列模块都用到了Callbacks对象。jQuery.ready函数在前面已经做了整理,所以这篇博客主要是分析Deffered(异步队列)和jQuery中异步队列的应用。
异步队列
延迟对象(异步队列)是在回调对象的基础上实现的。这个延迟对象维护了三个列表:成功(done)回调函数列表,失败(fail)回调函数列表和进行中(progress)回调函数列表,之所以有三个回调函数列表,是因为延迟对象有三种状态,分别是:resolve(成功), reject(失败), notify(进行中)。
那么延迟对象是如何在运用回调对象模块来实现异步功能的?
看一个例子:
var dfd = $.Deferred(); //与回调对象类似,先定义一个延迟对象;setTimeout( function(){ //当时间满足一秒时 dfd.resolve(); //相当于$.Callbacks().fire()方法,将加入到done(成功回调列表)中的函数执行,在这里将弹出有'Yes'的窗口}, 1000);dfd.done(function(){ //相当于$.Callbacks().add(function(){})方法//不同的是,这里分别通过done和fail方法,将不同的回调函数添加到各自的回调列表中。 alert('Yes');}).fail(function(){ alert('No'); //如果setTimeOut中,将dfd.resolve()换成dfd.reject(),将弹出有'No'的窗口})
根据上面分析,我们可以得到这样一个对应关系:
下来我们看看源码中是如何实现的?
源码框架
jQuery.extend({ Deferred: function( func ){ //三个回调函数列表 doneList = $.Callbacks('once memory') failList = $.Callbacks('once memory') progressList = $.Callbacks('memory') //初始状态 state = 'pending' //表示进行中 //三种状态和三个回调函数列表对应 list = { resolve: doneList, reject: failList, notify: progressList } promise = { done: doneList.add, fail: failList.add, progress: progressList.add, state: function(){ }, then: function(){ }, //依次添加doneList,failList和progressList回调函数 always: function(){ }, //不管触发resolve函数还是reject函数,都会执行该方法添加的回调函数 pipe: function(){ }, promise: function(){ } //返回promise对象的一个副本或者扩展的延迟对象 } //重新建一个promise的副本 deferred = promise.promise({}); //给deferred延迟对象添加三种状态函数 for(key in list) { deferred[key] = list[key].fire; deferred[key + 'With'] = list[key].fireWith; } //执行deferred.resolve或者deferred.reject时,要改变初始的状态,并且一旦状态确定,便不会再更改,所以有下面的操作 deferred.done(function(){ state = 'resolved' }, failList.disable, progressList.lock).fail(function(){ state = 'rejected' }, doneList.disable, progressList.lock); if( func ) { //Deferred也是支持参数的 func.call(deferred, deferred); } return deferred; //返回延迟对象 }})
在源码中,我们可以清楚的看到,延迟对象是如何利用回调对象来实现回调函数的添加和执行的。除了这些,可以看到,在Deferred函数中,创建了一个promise对象,另外还又得到了一个deferred对象,这两个对象之间有什么区别和联系呢?
可以看到,deferred对象由promise对象扩展而来,但是又比promise对象多了三个函数,这三个函数都是用于改变延迟对象状态的。那么为什么要用两个延迟对象? 这是为了保证外部不能对延迟对象的状态进行改变。
举个例子:
//函数a返回一个延迟对象function a(){ var dfd = $.Deferred(); setTimeout( function(){ dfd.resolve(); }, 1000); return dfd;}//将a得到的延迟对象 赋值给一个新的延迟对象var dfdNew = a().done(function(){ alert("Yes");}).fail(function(){ alert('No');});//造成在函数外部,对延迟对象的状态进行了改变。dfdNew.reject();
由于上面的a函数返回的是一个延迟对象deferred,所有包含有resolve,reject和notify方法,因此可以在函数外部去改变延迟对象状态,而此时我们是不希望外部对延迟对象的状态进行改变,因此这才利用到promise对象。看下面段代码:
function a(){ var dfd = $.Deferred(); setTimeout( function(){ dfd.resolve(); }, 1000); return dfd.promise();}var dfdNew = a().done(function(){ alert("Yes");}).fail(function(){ alert('No');});//此时得到dfdNew延迟对象,并没有resolve等改变状态的方法,因此下面的语句会出错。dfdNew.reject();
这段代码就按照我们的意愿来执行了,外部并不能对延迟对象的状态进行更改,只能通过a函数里面的dfd.resolve来改变。
异步队列的应用
在jQuery中,主要有两个部分用到了异步队列(延迟对象)模块:
$.ajax
和 $().ready()
方法。
在ready函数理解这篇博客中,整理了jQuery的ready方法,但当时还没有学习关于回调对象,延迟对象的模块,所以有一部分没有深入,下来我们再来缕一缕ready函数。
主要看readyList.add(fn)
这部分代码,readyList是一个回调对象,$.Callbacks('once memory')
,这就相当于Deferred对象中,成功回调函数列表、失败回调函数列表,在jQuery.fn.ready函数中,将fn回调函数添加进回调列表中。
在页面元素加载出来之后,触发之前注册的事件处理函数,这个事件处理函数中会去调jQuery.ready()
方法,在这个方法中调readyList.fireWith()
方法,从而实现回调函数的执行。
ajax
模块暂时还没有看,所以后期整理时再回过头来分析对于Deferred延迟对象的应用。
when方法
jQuery扩展了一个when
方法,这个方法相当于是对Deferred
对象的一个延伸,相当于对多个延迟对象组合起来进行处理。看下面一个例子:
function a(){ var d = $.Deferred(); d.resolve(); return d;}function b(){ var d = $.Deferred(); d.resolve(); return d;}$.when(a(), b()).done(function(){ //当a()和b()同时返回一个resolve状态的延迟对象时,才会触发doneList回调列表中的回调函数 alert("Yes");}).fail(function(){ //当a()或者b()任意一个返回reject状态的延迟对象,就会触发failList列表中的回调函数 alert("No");})//而当参数不是Deferred对象或者无参数时,始终触发doneList里面的回调函数;并且可以通过arguments类数组访问到传的参数。$.when(123, 8945).done(function(){ alert("成功");}).fail(function(){ alert("失败");})
有上面分析和测试,可以想到,$.when()
和$.when(123, 456)
是相同的处理;只有在参数为延迟对象时,才会判断这些延迟对象的状态,从而决定$.when
后面添加的回调方法执行哪一个。
下来根据源码缕一遍:
function( firstParam ) { //首先将参数转换成数组,调用[].slice方法 var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, //得到参数的个数 pValues = new Array( length ), count = length, pCount = length, //根据参数个数,以及参数类型,设置deferred对象 //当无参数或者多于一个参数时,deferred = $.Deferred()对象; //当且仅当参数是一个延迟对象类型时,deferred = 这个延迟对象 deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(), //deferred对象的一份拷贝,供外界使用,所以是利用promise方法得到,没有resolve(),reject()以及notify()方法,以为外界不允许对状态再进行改变 promise = deferred.promise(); //下面这两个函数先不看 function resolveFunc( i ) { } function progressFunc( i ) { } //下面利用参数个数分情况处理 if ( length > 1 ) { //多个参数时,又分当前参数是否为延迟对象 for ( ; i < length; i++ ) { //是延迟对象,分别向doneList列表,failList列表和progressList列表中添加回调。 //因为是延迟对象,即有'memory'标志的回调对象,如果之前延迟对象的状态已经确定,此时添加回调函数之后会立即执行回调函数。 //并且可以看到,添加到failList中的回调函数是deferred.reject,也就是说只要参数中有一个延迟对象的状态是reject,都会触发最终deferred对象的reject。 //而向doneList和progressList中添加的回调函数分别是resolveFunc和progressFunc,这两个函数后面再分析。 if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { //如果参数不是延迟对象,count减1. //这里说明一下count的含义。when整体的实现思路,是用一个计数器来记录是否所有的延迟对象都resolve或者都notify了。 //初始值为所有的参数个数,当参数不是延迟对象时,减一,当参数是延迟对象,且一直resolve或者一直notify,也减一。直到减为0时,触发deferred.resolve或者deferred.notify。 --count; } } if ( !count ) { //count减为0,触发deferred.resolve(). deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { //无参数时,触发deferred.resolveWith() //一个参数时,且参数不为延迟对象时,同样触发deferred.resolveWith(). deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } //一个参数,且参数为延迟对象时,deferred = 这个延迟对象。 //最后返回deferred.promise对象,也就是说,这个延迟对象的状态是什么,就去触发$.when后面添加的回调函数即可。类似于一个延迟对象的处理。 return promise;}
下面来说resolveFunc
和 progressFunc
这两个方法:
//这两个方法分别是添加进doneList和 progressList回调列表中的回调函数function resolveFunc( i ) { //返回一个函数,这个函数每一次对计数器count减一,并且判断在count为0时,直接触发deferred.resolve。 return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { deferred.resolveWith( deferred, args ); } };}//progressFunc与上面的函数是类似的,不同的是,这是对于progressList添加的回调函数//更重要的不同点是: 只要有一个延迟对象的状态为notify,就都会触发deferred.notify。function progressFunc( i ) { return function( value ) { pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; deferred.notifyWith( promise, pValues ); };}
到这里,关于回调对象,延迟对象以及延迟对象的扩展when都已经分析完了。下来为了加深对于这两个对象的理解和运用,后面可能会从jQuery
功能模块的ajax
方法来分析,期间对于回调对象和延迟对象的运用还会重点分析以加深理解。
- jQuery源码阅读(十三)---jQuery异步队列模块
- jQuery源码阅读(十四)---aJax 模块与异步队列联系
- jQuery源码分析-05异步队列 Deferred
- jquery源码阅读jQuery.inArray()
- Jquery:$Deffered() 异步队列(一)
- jQuery源码阅读有感
- jQuery源码阅读笔记
- jQuery源码阅读笔记
- jQuery源码阅读(一)---jQuery源码整体架构
- jQuery源码阅读(四)--正则表达式
- jQuery源码阅读(五)---init函数
- jQuery源码阅读(六)---jQuery实例方法解析
- jQuery源码阅读(八)---jQuery中的继承extend
- jQuery源码阅读(十)---jQuery静态方法分析
- jQuery源码阅读之获取jQuery对象
- jquery源码阅读之jquery.extend
- jQuery源码阅读之jQuery.Callbacks ()
- jQuery源码阅读[1]noConflict
- 逆反的01串 oj105
- LeetCode-338. Counting Bits (Java)
- C++中的引用
- swift4.0 UITableView纯代码实现
- hanoi塔经典递归算法
- jQuery源码阅读(十三)---jQuery异步队列模块
- 【USACO1.5.3】特殊的质数肋骨
- 简单Servlet+jsp 例子的逻辑思维分析
- Splay树模板
- Jam的计数法 oj106
- 关于cmd界面无法启动mysql
- POJ
- vitrualbox Ubuntu10.04 Server安装增强功能
- HDOJ2467