超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

Jquery整体架构

[TOC]

##1.理解架构

###1.1 最新jQuery2.1.1版本的结构:

;(function(global, factory) {
factory(global);
}(typeof window !== "undefined" ? window : this, function(window, noGlobal) {
var jQuery = function( selector, context ) {
222return new jQuery.fn.init( selector, context );
22};
22jQuery.fn = jQuery.prototype = {};
22// 核心方法
22// 回调系统
22// 异步队列
22// 数据缓存
22// 队列操作
22// 选择器引
22// 属性操作
22// 节点遍历
22// 文档处理
22// 样式操作
22// 属性操作
22// 事件体系
22// AJAX交互
22// 动画引擎
22return jQuery;
}));

###1.2 jQuery模块依赖
jQuery模块依赖

###1.3 jQuery的类数组对象结构
通过 $(".Class") 构建的对象结构如下所示:
jQuery模块依赖
整个结构很明了,通过对象键值对的关系保存着属性,原型保存着方法。我们来简单的模拟一个这样的数据结构:

var aQuery = function(selector) {
//强制为对象
2if (!(this instanceof aQuery)) {
22return new aQuery(selector);
2}
2var elem = document.getElementById(/[^#].*/.exec(selector)[0]);
2this.length = 1;
2this[0] = elem;
2this.context = document;
2this.selector = selector;
2this.get = function(num) {
22return this[num];
2}
2return this;
}

函数 aQuery() 内部首先保证了必须是通过 new 操作符构建。这样就能保证当前构建的是一个带有 this 的实例对象,既然是对象我们可以把所有的属性与方法作为对象的key与value的方式给映射到this上,所以如上结构就可以模拟出jQuery的这样的操作了,即可通过索引取值,也可以链式方法取值,但是这样的结构是有很大的缺陷的,每次调用ajQuery方法等于是创建了一个新的实例,那么类似get方法就要在每一个实例上重新创建一遍,性能就大打折扣,所以jQuery在结构上的优化不仅仅只是我们看到的,除了实现类数组结构、方法的原型共享,而且还实现方法的静态与实例的共存。

###1.4 jQuery中ready与load事件
jQuery有3种针对文档加载的方法:

$(document).ready(function() {
// ...代码...
})
//document ready 简写
$(function() {
// ...代码...
})
$(document).load(function() {
// ...代码...
})

DOM文档加载的步骤:

  1. 解析HTML结构。
  2. 加载外部脚本和样式表文件。
  3. 解析并执行脚本代码。
  4. 构造HTML DOM模型。//ready
  5. 加载图片等外部文件。
  6. 页面加载完毕。//load

ready与load的区别就在于资源文件的加载,ready构建了基本的DOM结构,所以对于代码来说应该越快加载越好。ready在第 4 步完成之后就执行了,但是load要在第 6 步完成之后才执行。

###1.5 jQuery多库共存处理
引入jQuery运行这个 noConflict 函数将变量 $ 的控制权让给第一个实现它的那个库,确保jQuery不会与其他库的$对象发生冲突。
在运行这个函数后,就只能使用jQuery变量访问jQuery对象。

Example:

//让出控制权
$.noConflict();

//使用noConflict后,$不存在
if (!$) {
2console.log("使用noConflict后,$不存在")
}

//使用noConflict后,jQuery存在
if (jQuery) {
2console.log("使用noConflict后,jQuery存在")
}

//通过闭包隔离出$,通过闭包隔离后,转为局部变量$存在
;(function($) {
2if ($) {
22console.log("通过闭包隔离后,转为局部变量$存在")
2}
})(jQuery);

实现原理:

Var _jQuery = window.jQuery,
_$ = window.$;

jQuery.noConflict = function( deep ) {
if ( window.$ === jQuery ) {
window.$ = _$;
}
2if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
};

jQuery在占用 $ 时,已经把之前的存在的命名空间给缓存起来, noConflict 通过对比当前的命名空间达到交换的目的: 首先,先判断下当前的 $ 空间是不是被jQuery接管了,如果是则让出控制权给之前的 _$ 引用的库,如果传入 deeptrue 的话等于是把 jQuery 的控制权也让出去了。

##2.核心模块

###2.1 分离构造器
通过new操作符构建一个对象,一般经过四步:

1.创建一个新对象
2.将构造函数的作用域赋给新对象(所以this就指向了这个新对象)
3.执行构造函数中的代码
4.返回这个新对象

最后一点就说明了,我们只要返回一个新对象即可。其实new操作符主要是把原型链跟实例的this关联起来,这才是最关键的一点,所以我们如果需要原型链就必须要new操作符来进行处理。否则this则变成window对象了。

我们来剖析下jQuery的这个结构,以下是我们常见的类式写法:

var $$ = ajQuery = function(selector) {
this.selector = selector;
return this
}
ajQuery.fn = ajQuery.prototype = {
selectorName:function(){
return this.selector;
},
constructor: ajQuery
}
var a = new $$('aaa'); //实例化
a.selectorName() //aaa //得到选择器名字

首先改造jQuery无new的格式,我们可以通过instanceof判断this是否为当前实例:

var $$ = ajQuery = function(selector) {
if(!(this instanceof ajQuery)){
return new ajQuery(selector);
}
this.selector = selector;
return this;
}

但是注意千万不要像下面这样写:

//错误 这样会无限递归自己,从而造成死循环并且溢出。
var $$ = ajQuery = function(selector) {
this.selector = selector;
return new ajQuery(selector);
}
Uncaught RangeError: Maximum call stack size exceeded

jQuery为了避免出现这种死循环的问题,采取的手段是把原型上的一个init方法作为构造器

var $$ = ajQuery = function(selector) {
//把原型上的init作为构造器
return new ajQuery.fn.init( selector );
}

ajQuery.fn = ajQuery.prototype = {
init: function() {
// init ajQuery;
},
constructor: jQuery
}

这样确实解决了循环递归的问题,但是又问题来了,init是ajQuery原型上作为构造器的一个方法,那么其this就不是ajQuery了,所以this就完全引用不到ajQuery的原型了,也就无法引用到ajQuery原型上定义的一系列方法。

###2.2 静态与实例方法共享设计
上一节提到分离后的构造器无法访问ajQuery原型上定义的一系列方法,jQuery给出如下方案:

ajQuery.fn = ajQuery.prototype = {
name: 'ajQuery',
init: function(selector) {
this.selector = selector;
return this;
},
constructor: ajQuery
}
ajQuery.fn.init.prototype = ajQuery.fn;

这段代码就是整个结构设计的最核心的东西了,
把jQuery.prototype原型的引用赋给jQuery.fn.init.prototype的原型,这样就把2个构造器的原型给关联起来了,整个结构就活了!不得不佩服作者的设计思路,别具匠心。

init的构造图:
init的构造图
通过原型传递解决问题,把jQuery的原型传递给jQuery.prototype.init.prototype。换句话说jQuery的原型对象覆盖了init构造器的原型对象,因为是引用传递所以不需要担心这个循环引用的性能问题。

###2.3 插件接口的设计
jQuery插件的开发分为两种:

  • 一种是挂在jQuery命名空间下的全局函数,也可称为静态方法;
  • 另一种是jQuery对象级别的方法,即挂在jQuery原型下的方法,这样通过选择器获取的jQuery对象实例也能共享该方法。

提供的接口:
$.extend(target, [object1], [objectN])

jQuery的代码实现:

jQuery.extend = jQuery.fn.extend = function() {
2var options,
22target = arguments[0] || {},
22i = 1,
22length = arguments.length,

2// Extend jQuery itself if only one argument is passed
2if ( i === length ) {
22target = this; //this决定这个方法是作为静态扩展还是实例扩展处理
22i--;
2}

2for ( ; i < length; i++ ) {
22// Only deal with non-null/undefined values
22if ( (options = arguments[ i ]) != null ) {
222// Extend the base object
22}
2}

2// Return the modified object
2return target;
};

从jQuery的源码中可以看到,jQuery.extend和jQuery.fn.extend其实是同指向同一方法的不同引用。
这里有一个设计的重点,通过调用的上下文,我们来确定这个方法是作为静态还是实例处理:

  • jQuery.extend 调用的时候,this是指向jQuery对象,所以这里扩展在jQuery上,作为静态方法。
  • jQuery.fn.extend 调用的时候,this指向jQuery.fn对象,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象,因此这里增加的是原型方法,也就是对象方法了。

接口的使用:

//挂在jQuery命名空间下的全局函数,扩展后可以通过jQuery.data()和jQuery.removeData()访问,
//但是通过选择器获取的对象实例jQuery()不具有此方法,jQuery().data() 和 jQuery().removeData()无法访问
jQuery.extend({
data: function(){},
removeData: function(){}
});

//挂在jQuery原型下的方法,扩展后可以通过jQuery的实例对象访问,jQuery().data() 和 jQuery().removeData()
//但是jQuery命名空间下不存在此方法,jQuery.data()和jQuery.removeData()无法访问
jQuery.fn.extend({
data: function(){},
removeData: function(){}
});

注意:

根据传入的参数不同,当参数仅仅传入一个对象时是对jquery功能的扩展,如果传入多个对象时,将用扩展传入的第一个对象,此过程与本节内容无关,不再讨论。

###2.4 回溯处理
jQuery每次dom操作会返回一个jQuery对象而非dom对象,jQuery对象实际上是对dom对象的一层包装,每个jQuery对象都有三个属性:contextselectorprevObject ,其中的 prevObject 属性就指向这个对象栈中的前一个对象,而通过这个属性可以回溯到最初的DOM元素集中。

在浏览器可以看到jQuery对象属性:

jQuery对象属性

jQuery内部维护着一个jQuery对象栈。每个遍历方法都会找到一组新元素(一个jQuery对象),然后jQuery会把这组元素推入到栈中。

jQuery为我们操作这个内部对象栈提供个非常有用的2个方法:

.end() //回溯到之前的Dom元素集合
.addBack()/.andSelf()(jQ1.8+) //回溯之前一个位置,然后把两个位置上的元素集组合起来,并把这个新的、组合之后的元素集推入栈的上方。

end() 方法主要用于jQuery的链式属性中。当没有使用链式用法时,我们通常只是调用变量名上的前一个对象,所以我们不需要操作栈。

Exapmple:

<ul class="first">
<li class="foo">list item 1</li>
<li class="bar">list item 2</li>
</ul>
<ul class="second">
<li class="foo">list item 1</li>
<li class="bar">list item 2</li>
</ul>
//设置.first .foo颜色黑色,.first .foo颜色红色
//
//首先在链式用法中只在第一个列表中查找样式为 foo 的项目,并将其颜色变成黑色。
//然后end()返回调用find()之前的状态。
//因此,第二次 find() 将只会查找 <ul class="first"> 中的 '.bar',
//而不是继续在<li class="foo">中进行查找,结果是将匹配到的元素的颜色变成红色。
$('.first').find('.foo').css('color', 'black').end().find('.bar').css('color', 'red');

//错误
//$('.first').find('.foo').css('color', 'black')后此时上下文已切换为.first .foo
//.find('.bar')无法找到该元素,故需要.end()回溯至上一步
$('.first').find('.foo').css('color', 'black').find('.bar').css('color', 'red');

jQuery的代码实现:

//可以看到end方法返回prevObject属性
end: function() {
return this.prevObject || this.constructor(null);
}

//在调用find方法查找元素时,通过pushStack方法构建jQuery对象,并返回
find: function(selector) {
2//...........................省略................................

//通过sizzle选择器,返回结果集
jQuery.find(selector, self[i], ret);

// Needed because $( selector, context ) becomes $( context ).find( selector )
ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret);
ret.selector = this.selector ? this.selector + " " + selector : selector;
return ret;
}

//pushStack对象,作用就通过新的DOM元素去创建一个新的jQuery对象
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );

// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;

// Return the newly-formed element set
return ret;
}

//constructor是指向构造器
jQuery.fn = jQuery.prototype = {
2//...........................省略................................
2constructor: jQuery,
}

流程解析:

  1. 首先构建一个新的jQuery对象,因为constructor是指向构造器的,所以这里就等同于调用jQuery()方法了,返回了一个新的jQuery对象;
  2. 然后用jQuery.merge语句把elems节点合并到新的jQuery对象上;
  3. 最后给返回的新jQuery对象添加prevObject属性,我们看到prevObject其实还是当前jQuery的一个引用罢了,所以也就是为什么通过prevObject能取到上一个合集的原因了。

addBack()addSelf() 方法与 end() 类似,多了一步合并当前对象的操作,不再赘述。

add: function( selector, context ) {
2return this.pushStack(
22jQuery.unique(
222jQuery.merge( this.get(), jQuery( selector, context ) )
22)
2);
},

addBack: function( selector ) {
2return this.add( selector == null ?
22this.prevObject : this.prevObject.filter(selector)
2);
}

jQuery.fn.andSelf = jQuery.fn.addBack;

###2.5 迭代器
迭代器是一个框架的重要设计。我们经常需要提供一种方法顺序用来处理聚合对象中各个元素,而又不暴露该对象的内部,这也是设计模式中的迭代器模式(Iterator)。针对迭代器,有几个特点:

  • 访问一个聚合对象的内容而无需暴露它的内部。
  • 为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
  • 遍历的同时更改迭代器所在的集合结构可能会导致问题。

除此之外,还要考虑支持以下至少四种情况:

  • 支持聚合对象,可能是对象,字符串或者数组等类型
  • 支持参数传递
  • 支持上下文的传递
  • 支持循环中退出

jQuery.each() 实现:

each: function( obj, callback, args ) {
2var value,
22i = 0,
22length = obj.length,
22isArray = isArraylike( obj );

2//支持参数传递
2if ( args ) {
22if ( isArray ) {
222for ( ; i < length; i++ ) {
2222value = callback.apply( obj[ i ], args );

2222if ( value === false ) {
22222break;
2222}
222}
22} else {
222for ( i in obj ) {
2222value = callback.apply( obj[ i ], args );

2222if ( value === false ) {
22222break;
2222}
222}
22}

2// A special, fast, case for the most common use of each
2} else {
22//支持对象,或者数组
22if ( isArray ) {
222for ( ; i < length; i++ ) {

2222//call直接把obj[i]作为上下文即callback回调中的this传递进去,
2222value = callback.call( obj[ i ], i, obj[ i ] );

2222//支持循环中退出
2222//根据回调的处理,从而判断是否要立刻中断这个循环,
2222//通过回调函数callback返回的ture/false的布尔值结果就可以来判断当前是否要强制退出循环。
2222if ( value === false ) {
22222break;
2222}
222}
22} else {
222for ( i in obj ) {
2222value = callback.call( obj[ i ], i, obj[ i ] );

2222if ( value === false ) {
22222break;
2222}
222}
22}
2}

2return obj;
},

jQuery的each方法从使用上就要分2种:

  • $.each()
  • $(selector).each()

$(selector).each() 的实现

each: function(callback, args) {
return jQuery.each(this, callback, args); //调用$.each()静态方法
},

jQuery可以是多个合集数组DOM,所以在处理的时候经常就针对每一个DOM都要单独处理,所以一般都需要调用 $(selector).each() 方法,接口的抽象合并在jQuery内部的运用很多,把相同功能的代码功能合并处理。

##3.回调Callbacks

###3.1 运用回调
我们经常会在这些情况使用函数回调:

  • 事件触发通知
  • 资源加载通知
  • 定时器延时
  • ajax、动画通知等等。

以上都是很单一的事件监听回调的处理方式,但是jQuery把回调函数的用法设计成一个更高的抽像,用于解耦与分离变化。

例如:

jQuery针对Dom的处理提供了appendprependbeforeafter 等方法的处理,这几个方法的特征:

  • 参数的传递可以是HTML字符串、DOM元素、元素数组或者jQuery对象
  • 为了优化性能针对节点的处理需要生成文档碎片

实现代码:

//callback 省略
//
append: function() {
2return this.domManip( arguments, callback);
},

prepend: function() {
2return this.domManip( arguments, callback);
},

before: function() {
2return this.domManip( arguments, callback);
},

after: function() {
2return this.domManip( arguments, callback);
}

domManip: function(args, callback) {
// Flatten any nested arrays
args = concat.apply([], args);
// We can't cloneNode fragments that contain checked, in WebKit
if (isFunction ||
//多参数处理
self.domManip(args, callback);
}

if (l) {
//生成文档碎片
fragment = jQuery.buildFragment(args, this[0].ownerDocument, false, this);
callback.call(this[i], node, i);
}
return this;
}

jQuery通过抽象出一个domManip方法,然后在这个方法中处理共性,合并多个参数的处理与生成文档碎片的处理,然后最终把结果通过回调函数返回给每一个调用者。

###3.2 观察者模式
观察者模式也即发布/订阅模式的背后,总的想法是在应用程序中增强松耦合性。并非是在其它对象的方法上的单个对象调用。一个对象作为特定任务或是另一对象的活动的观察者,并且在这个任务或活动发生时,通知观察者。

观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。

JS里对观察者模式的实现是通过回调来实现的,我们来先定义一个Observable对象,其内部包含了2个方法:订阅add方法与发布fire方法,如下代码:

var Observable = {
2callbacks: [],
2add: function(fn) {
22this.callbacks.push(fn);
2},
2fire: function() {
22this.callbacks.forEach(function(fn) {
222fn();
22})
2}
}

使用add开始订阅:

Observable.add(function() {
2alert(1)
})

Observable.add(function() {
2alert(2)
})

使用fire开始发布:

Observable.fire(); // 1, 2

###3.3 观察者模式运用
假设一段ajax的请求,成功后通过done返回结果数据:

$.ajax({
2url: "test.html",
2context: document.body
}).done(function(data) {
2//data数据的处理
2$('aaron1').html(data.a)
2$('aaron2').html(data.b)
2$('aaron3').html(data.c)
2//其余处理
});

所有的逻辑都写在done方法里面,虽然可以使用,但是问题就是逻辑太复杂了。Done里面有数据处理、html渲染、还可能有其它不同场景的业务逻辑。这样如果是换做不同的人去维护代码,增加功能就会显得很混乱而且没有扩展性。那么观察者模式能很好的解决了这个的问题。

试着优化代码:

$.ajax({
2url: "test.html",
2context: document.body
}).done(function(data) {
2pocessData()
2pocessHtml()
2pocessOther()
}

function pocessData() {
//处理数据
}

function pocessHtml() {
2$('aaron1').html(data.a)
2$('aaron2').html(data.b)
2$('aaron3').html(data.c)
}

function pocessOther() {
//处理其他逻辑
}

这种方式的好处是,分离出各种的业务函数,从而降低了代码之间的耦合度,但是这样代码写法几乎就是“就事论事”的处理,达不到抽象复用。

使用之前的观察者模式再次优化上面的代码:

Observable.add(function() {
2//pocessData
})

Observable.add(function() {
2$('aaron1').html(data.a)
2$('aaron2').html(data.b)
2$('aaron3').html(data.c)
})

Observable.add(function() {
2//pocessOther
})

$.ajax({
2url: "test.html",
2context: document.body
}).done(function(data) {
2Observable.fire(data)
})

设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知。订阅者也称为观察者,而被观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者并且可能经常以事件对象的形式传递消息。

###3.4 jQuery回调模块Callbacks
$.Callbacks是一个工厂函数,使用函数调用创建对象,它有一个可选参数 flags 用来设置回调函数的行为,对外的接口也就是self的返回。

jQuery.Callbacks()的API:

  • callbacks.add() : 回调列表中添加一个回调或回调的集合。
  • callbacks.disable() : 禁用回调列表中的回调。
  • callbacks.disabled() : 确定回调列表是否已被禁用。
  • callbacks.empty() : 从列表中删除所有的回调。
  • callbacks.fire() : 用给定的参数调用所有的回调。
  • callbacks.fired() : 访问给定的上下文和参数列表中的所有回调。
  • callbacks.fireWith() : 访问给定的上下文和参数列表中的所有回调。
  • callbacks.has() : 确定列表中是否提供一个回调。
  • callbacks.lock() : 锁定当前状态的回调列表。
  • callbacks.locked() : 确定回调列表是否已被锁定。
  • callbacks.remove() : 从回调列表中的删除一个回调或回调集合。

参数列表:

  • once : 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred)。
  • memory : 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred)。
  • unique : 确保一次只能添加一个回调(所以在列表中没有重复的回调)。
  • stopOnFalse : 当一个回调返回false 时中断调用。

实现代码:

jQuery.Callbacks = function(options) {
options = typeof options === "string" ?
(optionsCache[options] || createOptions(options)) :
jQuery.extend({}, options);
//实现代码
fire = function() {}
self = {
add: function() {},
remove: function() {},
has: function(fn) {},
empty: function() {},
disable: function() {},
disabled: function() {},
lock: function() {},
locked: function() {},
fireWith: function(context, args) {},
fire: function() {},
fired: function() {}
};
return self;
};

整个结构要分三部分:

  • Options参数缓存
  • 内部fire触发器的设计
  • 外部

参数的缓存设计:

// Callbacks是可以是接受的字符串的组合传参数,可以使用空格分割,
// 这样的操作其实是不需要重复的,可以设计一个缓存池,用来储存重复的操作
// jQuery把这些操作抽象出来作为内部通用工具代码
options = typeof options === "string" ?
22( optionsCache[ options ] || createOptions( options ) ) :
22jQuery.extend( {}, options );

// jQuery 通用的缓存池设计代码
// String to Object options format cache
var optionsCache = {};
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
2var object = optionsCache[ options ] = {};
2jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
22object[ flag ] = true;
2});
2return object;
}

// jQuery 通用的字符串转参数对象代码
var rnotwhite = (/\S+/g);
// String to Object options format cache
var optionsCache = {};
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
2var object = optionsCache[ options ] = {};
2jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
22object[ flag ] = true;
2});
2return object;
}

接口的设计:

callback需要在内部维护着一个list的队列数组,用于保存订阅的对象数据。同时也需要提供了add、remove、fire等订阅、发布、删除类似的接口。
可以构建一个存放回调的数组,如 var list = [] ,通过闭包使这条回调数组保持存在。添加回调时,将回调push进list,执行则遍历list执行回调。

##4.数据缓存

##5.deferred.js

###5.1 Deferred介绍
Deferred对象是由$.Deferred构造的,它用来解决JS中的异步编程,它遵循 Common Promise/A 规范。实现此规范的还有 when.js 和 dojo。

Deferred 提供了一个抽象的非阻塞的解决方案(如异步请求的响应),它创建一个promise对象,其目的是在未来某个时间点返回一个响应。简单来说就是一个异步/同步回调函数的处理方案。

$.Deferred作为新特性首次出现在版本1.5中,这个版本利用Deferred又完全重写了Ajax模块。$.Deferred在jQuery代码自身四处被使用,分别是promise方法、DOM ready、Ajax模块、动画模块。
deferred.js为jQuery对象提供了两个静态方法: $.Deferred()$.when()

jQuery的Deferred就是模块化程度非常高,可以混入任意的对象接口中配合使用:

function task(name) {
2var dtd = $.Deferred();
2setTimeout(function() {
22dtd.resolve(name)
2}, 1000)
2return dtd;
}
$.when(task('任务一'), task('任务二')).done(function() {
2alert('成功')
})

###5.2 用法

done/resolve 处理成功状态, fail/reject 处理失败状态, progress/notify 当前处理进度状态

调用示例:

function fn1() {
alert('success');
}
function fn2() {
alert('fail');
}
function fn3() {
alert('progress');
}

var deferred = $.Deferred();
deferred.done(fn1).fail(fn2).progress(fn3); // 链式操作

//耗时的异步操作
setTimeout(function() {
deferred.resolve();
//deferred.reject();
//deferred.notify();
}, 3000)

then ,一次添加成功,失败,进度回调函数,调用示例:

var deferred = $.Deferred();
deferred.then(fn1, fn2, fn3);

调用then后还可以继续链式调用then添加多个不同回调函数,这个then也正是jQuery对 Common Promise/A 的实现。

var deferred = $.Deferred();
deferred.then(fn1, fn2, fn3);

使用always方法为成功,失败状态添加同一个回调函数,回调函数中可以使用deferred.state方法获取异步过程中的最终状态,调用示例:

var deferred = $.Deferred()
deferred.always(function() {
var state = deferred.state()
if ( state === 'resolved') {
alert('success');
} else if (state === 'rejected') {
alert('fail');
}
})
setTimeout(function() {
deferred.resolve();
//deferred.reject();
}, 3000);

$.when() 保证多个异步操作全部成功后才回调,调用示例:

deferred.promise() 没有参数时,返回一个新的deferred对象;接受参数时,作用为在参数对象上部署deferred接口返回只能添加回调的对象,对象的运行状态无法被改变,即这个对象与$.Deferred()返回的对象不同,只能done/fail/progress,不能resolve/reject/notify。

###5.3 $.Deferred接口

$.Deferred的源码中对动作接口的定义:

[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]

实质上是观察者模式的实现:

|| 订阅方法 || 发布方法 ||
|| Done (操作完成) || resolve/resolveWith(解决) ||
|| Fail (操作失败) || reject/rejectWith(拒绝) || 30 ||
|| Progress (操作进行中) || notify/notifyWith(通知) ||

With接口扩展了3个可以定义上下文的发布方法

$.when

$.when接受若干个对象,参数仅一个且非Deferred对象将立即执行回调函数,Deferred对象和非Deferred对象混杂时,对于非Deferred对象remaining减1

Deferred对象总数 = 内部构建的Deferred对象 + 所传参数中包含的Deferred对象,所传参数中所有Deferred对象每当resolve时remaining减1,直到为0时(所有都resolve)执行回调