一、什么是deferred对象
开发网站的过程中,我们经常遇到耗时很长的JavaScript操作,其中既有异步操作(比如ajax读取服务器数据),也有同步操作(比如遍历一个大型数组),他们都不是立即能的到结果的。
通常的做法是,为他们指定回调函数,即事先规定,一但他们运行结束,应该调用哪些函数。但是在回调函数方面,jQuery的功能非常弱,为了改变这一点,jQuery开发团队就设计了deferred对象
简单说,deferred对象就是jQuery的回调函数解决方案在英语中,defer的意思是‘延迟’,所以deferred对象的含义就是‘延迟到未来某个点在执行’
二、ajax操作的链式写法
ajax操作的传统写法:
$.ajax({
url : 'text.html',
success : function(){
alert('成功了')
},
errer : function(){
alert('失败了~')
}
})
在上面的代码中,$.ajax接收一个对象参数,这个对象包含了两个方法,success方法指定操作成功的回调函数,errer方法指定操作失败的回调方法
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。
eg :
$.ajax('text.html')
.done(function(){ alert('成功了') })
.fail(function(){ alert('失败了~') })
//done()相当于success方法
//fail()相当于errer方法
三、指定同一操作的多个函数
deferred对象有一大好处,就是他允许你自由添加多个回调函数。
以上面的代码为例,如果ajax操作成功后,除了原来的回调函数,我还想在运行一个回调函数,怎么办?直接加在后面就ok了。
$.ajax('text.html')
.done(function(){ alert('成功了') })
.fail(function(){ alert('失败了~') })
.done(function(){ alert('这是第二个回调函数') })
四、为多个操作指定回调函数
deferred对象的另一个好处,就是可以为多个事件指定一个回调函数,这个传统写法是做不到的。
用到了新的方法 $.when( )
$.when($.ajax('text1.html') , $.ajax('text2.html'))
.done(function(){ alert('成功了') })
.fail(function(){ alert('失败了~') })
这段代码的意思是先执行两个操作,要是都成功了就执行done( )指定的回调函数;如果有一个失败或者都失败了,就执行fail( )指定的回调函数
五、普通操作的回调函数接口
deferred对象的最大优点,就是他把这一套回调函数接口,从ajax扩展到了所有操作,也就是说,任何一个操作,不管是ajax还是本地操作,也不管是异步还是同步操作,都可以使用deferred对象的各种方法,指定回调函数。
eg : 假设有一个很耗时的wait
var wait = function (){
var task = function(){
alert('task成功')
}
setTimeout(task, 5000);
}
我们为它指定回调函数,应该怎么做,很自然的你会想到,使用$.when( )
$.when(wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
但是这样写的话,done( )方法会立即执行,起不到回调函数的作用。原因在于$.when( )的参数只能是deferred对象,所以必须对wait( )进行改写
var dtd = $.Deferred(); //新建一个deferred对象
var wait = function (dtd){
var task = function(){
alert('task成功')
dtd.resolve(); //改变deferred对象的执行状态,已完成状态
//dtd.reject() 失败状态会调用fail()
}
setTimeout(task, 5000);
return dtd
}
现在wait( )函数返回的是deferred对象了,就可以加上链式操作了
$.when(wait(dtd))
.done(function(){ alert('成功') })
.fail(function(){ alert('失败') })
wait( )函数运行完毕,就可以执行done( )方法执行的回调函数了
六、deferred.resolve( )方法和deferred.reject( )方法
如果仔细看,你会发现在上面的wait()函数中,还有一个地方我没讲解。那就是dtd.resolve()的作用是什么?
要说清楚这个问题,就要引入一个新概念”执行状态”。jQuery规定,deferred对象有三种执行状态—-未完成,已完成和已失败。如果执行状态是”已完成”(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是”已失败”,调用fail()方法指定的回调函数;如果执行状态是”未完成”,则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。
前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。
dtd.resolve()的意思是,将dtd对象的执行状态从”未完成”改为”已完成”,从而触发done()方法。
七、deferred.promise( )方法
上面的写法,还是有问题,那就是dtd是一个全局对象,所以他的执行状态可以从外部改变。
请看下面的代码 :
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
dtd.resolve();
我们在尾部加一行dtd.resolve(),这就改变了dtd的执行状态,因此导致done()方法立刻执行,跳出”哈哈,成功了!”的提示框,等5秒之后再跳出”执行完毕!”的提示框。
为了避免这种情况,jQuery提供了deferred.promise()方法,他的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变状态执行无关的方法(比如done()方法和fail()方法),屏蔽与改变状态有关的方法(比如resolve()和reject()方法),从而使得执行状态不能被改变。
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd.promise(); // 返回promise对象
};
var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
$.when(d)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
d.resolve(); // 此时,这个语句是无效的
不过更好的写法是把dtd对象变成wait()函数的内部对象
var wait = function(dtd){
var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd.promise(); // 返回promise对象
};
$.when(wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
此文章摘自阮一峰的网络日志 ,链接:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html