一、前言
jQuery.Deferred作为1.5的新特性出现在jQuery上,而jQuery.ajax函数也做了相应的调整。因此我们能如下的使用xhr请求调用,并实现事件处理函数晚绑定。
我还一度以为这就是Promises/A+规范的实现,但其实jQuery.Deferred应该与jsDeferred归为一类,我称之为Before Promises/A。虽然jQuery.Deferred的出现会导致初接触Promise的朋友产生不少的误解,但同时证明了Promises/A+规范的实现已成为开发过程中必不可少的利器了。
接下来我们会踏上从1.5到2.1版本的jQuery.Deferred实现的剖析之旅,有兴趣的朋友们请坐稳扶好哦!!!
由于篇幅较长,特设目录一坨!
jQuery.Deferred 中主要包含三个对象类型Deferred、EnhancedDeferred和Promise,Deferred作为基础类型用于构建更复杂的EnhancedDeferred类型,EnhancedDeferred实例则是用户直接操作的对象,而Promise则是EnhancedDeferred的功能子集,仅提供成功/失败回调函数的订阅、关联的EnhancedDeferred实例的状态查询功能。
Deferred实例的状态:initialized 、fired和cancelled。而状态间的转换关系如下:
initialized -> fired initalized -> cancelled
EnhancedDeferred实例的状态:initialized、resolved、rejected。而状态间的转换关系如下:
initialized -> resolved initialized -> rejected
(注意:上述类型和类型状态均根据源码分析得出,源码中并没有明确注明)
1.5的jQuery.Deferred实现位于core.js文件中的,下面我将相关代码抽取并分组来分析。
1. Deferred实例工厂


Deferred实例内部维护着名为callbacks的回调函数队列(而不是Promises/A+规范中的成功/失败事件处理函数和Deferred单向链表)。然后将目光移到done方法,透过其实现可知jQuery.Deferred是支持回调函数晚绑定的(jsDeferred不支持,Promises/A+规范支持),但均以resovleWith的参数作为回调函数的入参,而不是上一个回调函数的返回值作为下一个回调函数的入参来处理,无法形成责任链模式(Promises/A+规范支持)。
2. 对外API——jQuery.Deferred


jQuery.Deferred函数返回一个EnhancedDeferred实例,而EnhancedDeferred是以一个管理成功回调函数队列的Deferred实例为基础,并将另一个用于管理失败回调函数队列的Deferred实例作为EnhancedDeferred实例扩展功能的实现提供者,很明显成功、失败回调函数队列是独立管理和执行。
3. 辅助方法——jQuery.when
功能就是等待所有入参均返回值后,以这些返回值为入参调用回调队列的函数


jQuery.Deferred中的Deferred实例和EnhancedDeferred实例均设计了隐式的状态标识,因此支持回调函数晚绑定的功能,但由于其采用两个Deferred实例分类管理所有成功/失败回调函数,而不是采用Deferred实例单向链表的结构,因此无法实现成功和失败回调函数之间的数据传递,并且没有对回调函数的抛异常的情况作处理。并且resolveWith的遍历调用回调函数队列中没有采用责任链模式,与Promises/A+规范截然不同。另外回调函数均为同步调用,而不是Promises/A+中的异步调用。因此我们只能将其列入Before Promises/A的队列中了!
jQuery1.5除了新增jQuery.Deferred特性,还以jQuery.Deferred为基础对ajax模块进行增强,相关代码如下:


可能是jQuery的开发团队意识到jQuery.Deferred的实现与Promises/A+规范相距甚远,于是在1.6版本上补丁式地为EnhancedDeferred增加了一个 pipe方法 ,从而实现回调函数的责任链。另外jQueyr.Deferred已经成为一个独立的模块deferred.js了(《JavaScript框架设计》中的示例就是1.6的)。


除了pipe函数外,1.6还为EnhancedDeferred实例新增了 always函数 ,通过它添加的回调函数,无论EnhancedDeferred实例状态为"resolved"还是"rejected"均会被执行。
另外1.6对$.when进行了重构使代码更容易理解。并且effectes和queue模块可以开始以jQuery.Deferred作为基础提供then方法等API了。
由于VS2012新建Asp.Net项目时默认自带jQuery1.7,我想Asp.Net的攻城狮们对它应该不陌生了。而1.7版本的jQuery.Deferred相对于以前的版本新增了 progress 、 notify 和 notifyWith 的API,但到底有什么用呢?1.7版本的jQuery.Deferred是否更接近Promises/A+规范呢?答案是否定的。
新版的jQuery.Deferred内部新增一个回调函数队列,该队列不像1.6版本中的deferred和failDeferred那样只能触发一次"initialized"->"fired"的状态转换,而是可以进行多次并且与deferred和failDeferred一样支持回调函数晚绑定。而 progress 、 notify 和 notifyWith 则与这个新的回调函数队列相关。
另外1.7版本中对jQuery.Deferred进行全局重构,不再由原来的 $._Deferred 来构建Deferred实例,而是通过 jQuery.Callbacks函数 来生成回调函数队列管理器来代替(作用是一样的,但回调函数队列管理器更具有通用性),而上文提到的EnhancedDeferred则由三个回调函数队列管理器组成。
在陷入源码前再次强调一点——1.7与1.6版本在本质上是一点都没变!!
1. 首先我们一起来看看重构的重心—— jQuery.Callbacks函数 (位于callbacks.js文件中)
作用:创建回调函数队列管理器实例。
回调函数队列管理器存在以下状态:
initialized: 管理器实例初始状态;
firing: 正在遍历回调函数队列并按FIFO顺序调用回调函数;
fired: 遍历完回调函数队列,等待接受下一次遍历请求;
locked: 锁定管理器,无法再接受遍历回调函数的请求;
dying: 管理器进入临死状态,只要此时状态转换为fired或locked,则会直接跳转为disabled状态;
disabled: 管理器将被废弃,无法再使用了。
状态间的转换关系如下:
①. initialized -> firing <-> fired [-> disabled|locked] ②. initialized <-> firing <-> fired [-> disabled|locked] ③. initialized -> locked -> disabled ④. initialized -> dying -> locked -> disabled ⑤. initialized -> dying -> fired -> disabled ⑥. initialized -> dying -> fired -> firing
在调用jQuery.Callbacks时可以通过可选入参来配置管理器的一些特性,分别为:
unique,是否确保队列中的回调函数的唯一性。
stopOnFalse,是否当某个回调函数返回值为false时,将配置管理器的状态设置为dying。
once,是否仅能执行一次队列遍历操作。若不限制仅能执行一次队列遍历(默认值),则状态转换关系为②、③和⑥。
memory,是否支持函数晚绑定。若不支持晚绑定且仅能执行一次队列遍历操作,则状态转换关系为③、④和⑤。若支持晚绑定则为①和③。


2. 然后就是jQuery.Deferred的改造


1.7中通过 私有属性state 明确标识Deferred实例的状态(pending、resolved和rejected),但可惜的是这些属性对Deferred实例的行为没有任何作用,感觉有没有这些状态都没有所谓。
经过这样一改,就更明确Deferred实例其实对三个回调函数队列的统一管理入口而已了。
jQuery1.8的jQuery.Deferred依然依靠jQuery.Callbacks函数生成的三个回调函数队列管理器作为Deferred的构建基础,该版本大部分均为对jQuery.Deferred和jQuery.Callbacks代码结构、语义层面的局部重构,使得更容易理解和维护,尤其是对jQuery.Callbacks代码重构后,回调函数队列管理器实例的状态关系转换清晰不少。
而比较大的局部功能重构是jQuery.Deferred的then方法被重构成为pipe方法的别名,而pipe函数的实现为Promise/A规范中的then方法,因此1.8的then方法与旧版本的then方法不完全兼容。
jQuery1.9和2.1并没重构或为jQuery.Deferred添加新功能,可以直接跳过。
通过上述内容大家已经清楚jQuery.Deferred并不是Promise/A+规范的完整实现(甚至可以说是相距甚远),且jQuery1.8中then函数的实现方式与旧版本的不同,埋下了兼容陷阱,但由于jQuery.Deferred受众面少(直接使用Ajax、effects和queue模块的Promise形式的API较多),因此影响范围不大,庆幸庆幸啊!
《JavaScript架构设计》
如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!
<a href="http://home.cnblogs.com/u/fsjohnhuang/">^_^肥仔John</a>
<a href="http://home.cnblogs.com/u/fsjohnhuang/followees">关注 - 85</a>
<a href="http://home.cnblogs.com/u/fsjohnhuang/followers">粉丝 - 707</a>
<a>+加关注</a>
<a></a>
<a href="http://www.ucancode.com/index.htm" target="_blank">【推荐】超50万VC++源码: 大型工控、组态\仿真、建模CAD源码2018!</a>
<a href="https://cloud.tencent.com/developer/support-plan?fromSource=gwzcw.710852.710852.710852" target="_blank">【推荐】加入腾讯云自媒体扶持计划,免费领取域名&服务器</a>
<b>最新IT新闻</b>:
<b>最新知识库文章</b>:
<a href="https://github.com/fsjohnhuang" target="_blank">肥仔John@github</a>
作品:
<a href="https://github.com/fsjohnhuang/iScheme" target="_blank">iScheme—Scheme解释器</a>
<a href="https://github.com/fsjohnhuang/preview" target="_blank">preview.js—纯前端的图片预览组件</a>