jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)
Deferred的概念请看第一篇
http://www.cnblogs.com/aaronjs/p/3348569.html
******************构建Deferred对象时候的流程图**************************
**********************源码解析**********************
因为callback被剥离出去后,整个deferred就显得非常的精简
jQuery.extend({ Deferred : function(){} when : function() )}
对于extend的继承这个东东,在之前就提及过jquery如何处理内部jquery与init相互引用this的问题
对于JQ的整体架构一定要弄懂 http://www.cnblogs.com/aaronjs/p/3278578.html
所以当jQuery.extend只有一个参数的时候,其实就是对jQuery静态方法的一个扩展
我们在具体看看2个静态方法内部都干了些什么:
Deferred整体结构:
源码精简了部分代码
Deferred: function( func ) {
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") ]
],
state = "pending",
promise = {
state: function() {},
always: function() {},
then: function( /* fnDone, fnFail, fnProgress */ ) { },
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {}
},
deferred = {};
jQuery.each( tuples, function( i, tuple ) {
deferred[ tuple[0] + "With" ] = list.fireWith;
});
promise.promise( deferred );
// All done!
return deferred;
},
- 显而易见Deferred是个工厂类,返回的是内部构建的deferred对象
- tuples 创建三个$.Callbacks对象,分别表示成功,失败,处理中三种状态
- 创建了一个promise对象,具有state、always、then、primise方法
- 扩展primise对象生成最终的Deferred对象,返回该对象
这里其实就是3个处理,但是有个优化代码的地方,就是把共性的代码给抽象出来,通过动态生成了
具体源码分析:
Deferred自身则围绕这三个对象进行更高层次的抽象
- 触发回调函数列表执行(函数名)
- 添加回调函数(函数名)
- 回调函数列表(jQuery.Callbacks对象)
- 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") ]
],
这里抽象出2组阵营:
1组:回调方法/事件订阅
2组:通知方法/事件发布
tuples 元素集 其实是把相同有共同特性的代码的给合并成一种结构,然后通过一次处理
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
promise[ tuple[1] ] = list.add;
if ( stateString ) {
list.add(function() {
state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
对于tuples的3条数据集是分2部分处理的
第一部分将回调函数存入
promise[ tuple[1] ] = list.add;
其实就是给promise赋予3个回调函数
promise.done = $.Callbacks("once memory").add
promise.fail = $.Callbacks("once memory").add
promise.progressl = $.Callbacks("memory").add
如果存在deferred最终状态
默认会预先向doneList,failList中的list添加三个回调函数
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 );
}
*************************************************************
这里有个小技巧
i ^ 1 按位异或运算符
所以实际上第二个传参数是1、0索引对调了,所以取值是failList.disable与doneList.disable
*************************************************************
通过stateString有值这个条件,预先向doneList,failList中的list添加三个回调函数
分别是:
doneList : [changeState, failList.disable, processList.lock]
failList : [changeState, doneList.disable, processList.lock]
- changeState 改变状态的匿名函数,deferred的状态,分为三种:pending(初始状态), resolved(解决状态), rejected(拒绝状态)
- 不论deferred对象最终是resolve(还是reject),在首先改变对象状态之后,都会disable另一个函数列表failList(或者doneList)
- 然后lock processList保持其状态,最后执行剩下的之前done(或者fail)进来的回调函数
所以第一步最终都是围绕这add方法
- done/fail/是list.add也就是callbacks.add,将回调函数存入回调对象中
第二部分很简单,给deferred对象扩充6个方法
- resolve/reject/notify 是 callbacks.fireWith,执行回调函数
- resolveWith/rejectWith/notifyWith 是 callbacks.fireWith 队列方法引用
最后合并promise到deferred
promise.promise( deferred );
jQuery.extend( obj, promise )
所以最终通过工厂方法Deferred构建的异步对象带的所有的方法了
return 内部的deferred对象了
由此可见我们在
var defer = $.Deferred(); //构建异步对象
的时候,内部的对象就有了4个属性方法了
- deferred: Object
- always: function () {
- done: function () {
- fail: function () {
- notify: function () {
- notifyWith: function ( context, args ) {
- pipe: function ( /* fnDone, fnFail, fnProgress */ ) {
- progress: function () {
- promise: function ( obj ) {
- reject: function () {
- rejectWith: function ( context, args ) {
- resolve: function () {
- resolveWith: function ( context, args ) {
- state: function () {
- then: function ( /* fnDone, fnFail, fnProgress */ ) {
- promise: Object
- always: function () {
- done: function () {
- fail: function () {
- pipe: function ( /* fnDone, fnFail, fnProgress */ ) {
- progress: function () {
- promise: function ( obj ) {
- state: function () {
- then: function ( /* fnDone, fnFail, fnProgress */ ) {
- state: "pending"
- tuples: Array[3]
构造图
以上只是在初始化构建的时候,我们往下看看动态执行时候的处理
*****************执行期***********************
一个最简单的demo为例子
var d = $.Deferred(); setTimeout(function(){
d.resolve(22)
},0); d.then(function(val){
console.log(val);
})
换句话说,我们调用d.resolve(22) 就等于是调用
匿名函数并传入参数值 22
function(val){
console.log(val); //22
}
当前实际的使用中会有各种复杂的组合情况,但是整的外部调用流程就是这样的
***************** resolve的实现 *******************
我们回顾下,其实Deferred对象,内部的实现还是Callbacks对象,只是在外面再封装了一层API,供接口调用
d.resolve(22)
实际上调用的就是通过这个代码生成的
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
deferred.resolveWith()
最终执行的就是 list.fireWith
所以最终又回到回调对象callbacks中的私有方法fire()了
Callbacks会通过
callbacks.add()
把回调函数给注册到内部的list = []上,我们回来过看看
deferred.then()
d.then(function(val){
console.log(val);
})
***************** then的实现 *******************
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
//省略............
});
});
fns = null;
}).promise();
},
- 递归jQuery.Deferred
- 传递了func
- 链式调用了promise()
因为在异步对象的方法都是嵌套找作用域属性方法的
这里我额外的提及一下作用域
var d = $.Deferred();
这个异步对象d是作用域是如何呢?
第一层:无可争议,浏览器环境下最外层是 window
第二层:jquery本身是一个闭包
第三层: Deferred工厂方法产生的作用域
如果用d.then()方法呢?
很明显then方法又是嵌套在内部的函数,所以执行的时候都默认会包含以上三层作用域+自己本身函数产生的作用域了
我们用个简单图描绘下
根据规则,在最内部的函数能够访问上层作用域的所有的变量
我们先从使用的层面去考虑下结构设计:
demo 1
var defer = $.Deferred(); var filtered = defer.then(function( value ) {
return value * 2;
}); defer.resolve( 5 ); filtered.done(function( value ) {
console.log(value) //10
});
demo 2
var defer = $.Deferred(); defer.then(function(value) {
return value * 2;
}).then(function(value) {
return value * 2;
}).done(function(value) {
alert(value) //
}); defer.resolve( 5 );
其实这里就是涉及到defer.then().then().done() 链式调用了
API是这么定义的:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
我们抓住几点:
- 返回的是新的promise对象
- 内部有一个滤器函数
从demo 1中我们就能看到
经过x.then()方法处理的代码中返回的this(filtered ),不是原来的$.Deferred()所有产生的那个异步对象(defer )了
所以,每经过一个then那么内部处理的this都要被重新设置,那么为什么要这样处理呢?
源码
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
//分别为deferred的三个callbacklist添加回调函数,根据fn的是否是函数,分为两种情况
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
在Deferred传递实参的时候,支持一个flag,jQuery.Deferred(func)
传递一个回调函数
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
所以newDefer可以看作是
newDefer = $.Deferred();
那么func回调的处理的就是过滤函数了
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
这里其实也有编译函数的概念,讲未来要执行的代码,预先通过闭包函数也保存起来,使其访问各自的作用域
第一步
分解tuples元素集
jQuery.each( tuples, function( i, tuple ) {
//过滤函数第一步处理
})
第二步
分别为deferred[ done | fail | progress ]执行对应的add方法,增加过滤函数给done | fail | progress 方法
deferred[ tuple[1] ](
传入过滤函数
)//过滤函数 执行的时候在分解
代码即
deferred[done] = list.add = callback.add
第三步
返回return jQuery.Deferred().promise()
此时构建了一个新的Deferred对象,但是返回的的是经过promise()方法处理后的,返回的是一个受限的promise对象
所以整个then方法就处理了2个事情
- 构建一个新的deferred对象,返回受限的promise对象
- 给父deferred对象的[ done | fail | progress ]方法都增加一个过滤函数的方法
我们知道defer.then方法返回的是一个新的jQuery.Deferred().promise()对象
那么我们把defer.then返回的称之为子对象,那么如何与父对象var defer = $.Deferred() 关联的起来的
我看看源码
deferred[ tuple[1] ](//过滤函数//)
deferred其实就是根级父对象的引用,所以就嵌套再深,其实都是调用了父对象deferred[ done | fail | progress 执行add罢了
从图中就能很明显的看到 2个不同的deferred对象中 done fail progress分别都保存了不同的处理回调了
deferred.resolve( args )
- 当延迟对象被 resolved 时,任何通过
deferred.then
或deferred.done
添加的 doneCallbacks,都会被调用 - 回调函数的执行顺序和它们被添加的顺序是一样的
- 传递给
deferred.resolve()
的args
参数,会传给每个回调函数 - 当延迟对象进入 resolved 状态后,再添加的任何 doneCallbacks,当它们被添加时,就会被立刻执行,并带上传入给
.resolve()
的参数
流程如图
流程解析:
1 执行fire()方法,递归执行list所有包含的处理方法
2 执行了默认的 changeState, disable, lock 方法、
3 执行过滤函数
根据 var returned = fn.apply( this, arguments )的返回值(称作returnReferred)是否是deferred对象
- 返回值是deferred对象,那么在returnReferred对象的三个回调函数列表中添加newDeferred的resolve(reject,notify)方法,也就是说newDeferrred的执行依赖returnDeferred的状态
- 不是函数的情况(如值为undefined或者null等),直接链接到newDeferred的resolve(reject,notify)方法,也就是说 newDeferrred的执行依赖外层的调用者deferred的状态或者说是执行动作(resolve还是reject或者是notify) 此时deferred.then()相当于将自己的callbacklist和newDeferred的callbacklist连接起来
下面就是嵌套deferred对象的划分了
源码还是要靠自己去折腾的
思想的提高比较难的,我们可以借鉴设计的思路,代码书写方式都是有益无害的
流程的分析已经比较透彻了,下一章在讲解when的实现
写这东西太耗精力了,如果对您有帮助,请点击推荐支持一下……………
jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)的更多相关文章
- jQuery 2.0.3 源码分析 Deferred概念
JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而回调函数中则包含了后续的工作.这也是造成异步编程困难的主要原因:我们一直习惯于“线性”地编写代码 ...
- jQuery 2.0.3 源码分析Sizzle引擎解析原理
jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...
- jQuery 2.0.3 源码分析 Deferrred概念
转载http://www.cnblogs.com/aaronjs/p/3348569.html JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而 ...
- jQuery 2.0.3 源码分析core - 选择器
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 打开jQuery源码,一眼看去到处都充斥着正则表达式,jQuery框架的基础就是查询了,查询文档元素对象 ...
- jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on
事件(Event)是JavaScript应用跳动的心脏,通过使用JavaScript ,你可以监听特定事件的发生,并规定让某些事件发生以对这些事件做出响应 事件的基础就不重复讲解了,本来是定位源码分析 ...
- jQuery 2.0.3 源码分析 事件体系结构
那么jQuery事件处理机制能帮我们处理那些问题? 毋容置疑首先要解决浏览器事件兼容问题 可以在一个事件类型上添加多个事件处理函数,可以一次添加多个事件类型的事件处理函数 提供了常用事件的便捷方法 支 ...
- jQuery 2.0.3 源码分析core - 整体架构
拜读一个开源框架,最想学到的就是设计的思想和实现的技巧. 废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 编译函数(大篇幅)
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 从Sizzle1.8开始,这是Sizzle的分界线了,引入了编译函数机制 网上基本没有资料细说这个东东的,sizzle引入这 ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + div.aaron input[type="checkb ...
随机推荐
- C 标准库系列之float.h
float.h 内部主要包含了一系列的浮点数宏.指明可移植程序必要的常量:浮点数格式一般为Spxbe;其中S表示+-:p表示底数.b表示基数如2.8.10.16等进制,e为指数标识E或e: 在一般情况 ...
- My year of 2016
2016, year of excellence. Year of happiness. In Beijing we can also find some happiness which is s ...
- 使用sublimehighlight 将文本 转化html
a = "aaa" b = "bbb" c = "ccc" final = a + b + c print final import sys ...
- java.lang.OutOfMemoryError: PermGen space错误解决方法
1. MyEclipse 中报 PermGen space window--> preferences-->Myclipse-->Servers-->Tomcat- ...
- Web Api 模型验证
1.模型建立,在模型上类上添加System.ComponentModel.DataAnnotations验证属性 public class Product { public int Id { get; ...
- c和oc小知识
1.const const 修饰了*p1 / *p2 const int * p1=&age; int const * p2=&age;//和上面的意义一样 ,换句话说就是 在 “ * ...
- iOS中一些算法函数
rand() --- 随机数 求随机数 a-b arc4random()%(b-a+1)+a abs() labs() ---- 整数绝对值 fabs() fabsf() fabsl() ...
- MongoDB 由于目标计算机积极拒绝,无法连接 2014-07-25T11:00:48.634+0800 warning: Failed to connect to 127.0.0.1:27017, reason: errno:10061
转载自:http://www.cnblogs.com/xiaoit/p/3867573.html 1:启动MongoDB 2014-07-25T11:00:48.634+0800 warning: F ...
- Sql判断不为Null也不为空的写法
看到不少人写: isnull(field,'')<>'' 其中这样写最经济实惠:field>''
- python中类的三种属性
python中的类有三种属性:字段.方法.特性 字段又分为动态字段和静态字段 类 class Province: #静态字段 memo = 'listen' #动态字段 def __init__(se ...