概览
$.Deferred() 是 jQuery 中实现异步编程的核心机制,它提供了一种统一的方式来管理异步操作的状态和回调函数。一个jQuery延迟对象代表一个尚未完成的操作,并允许绑定多个成功、失败或进度通知的回调。自 jQuery 1.5 引入以来,$.Deferred() 极大地改变了处理异步任务(如 Ajax 请求、动画和自定义耗时操作)的方式,为代码带来了更高的可读性和可维护性。本章节将深入解析 jQuery.Deferred() 的工作原理、核心方法以及与现代 Promise 的关联。
创建与理解 Deferred 对象
$.Deferred() 是一个工厂函数,用于创建一个新的 延迟对象。该对象内部维护着操作的状态和回调队列。通过操作这个对象,可以控制异步流程。
语法
var deferred = $.Deferred( [beforeStart] );- beforeStart(可选):一个函数,在构造器内部、返回 Deferred 对象之前被调用。该函数接收新创建的 Deferred 对象作为参数,可用于执行一些初始化逻辑。
状态
一个 Deferred 对象 始终处于以下三种状态之一:
- pending(待定):初始状态,操作尚未完成,也未失败。
- resolved(已解决):操作成功完成,调用了
resolve或resolveWith。 - rejected(已拒绝):操作失败,调用了
reject或rejectWith。
一旦状态变为 resolved 或 rejected,它将被锁定,无法再改变。可以额外添加一个进度通知机制,通过 notify 和 progress 回调来报告中间状态,这不影响最终状态的变更。
示例:创建一个基础的 Deferred 对象
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>创建 Deferred 对象</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<button id="startTask">启动耗时任务</button>
<div id="status"></div>
<script>
function simulateAsyncTask() {
var deferred = $.Deferred();
var timeLeft = 3;
// 模拟异步操作(例如 setTimeout)
var intervalId = setInterval(function() {
timeLeft--;
deferred.notify('任务进行中,剩余 ' + timeLeft + ' 秒...');
if (timeLeft === 0) {
clearInterval(intervalId);
deferred.resolve('任务完成!');
}
}, 1000);
return deferred.promise(); // 返回 Promise 对象,防止外部修改状态
}
$('#startTask').on('click', function() {
var $status = $('#status');
$status.html('开始任务...');
var promise = simulateAsyncTask();
promise.progress(function(message) {
$status.append('<p>' + message + '</p>');
}).done(function(message) {
$status.append('<p><strong>' + message + '</strong></p>');
}).fail(function(message) {
$status.append('<p style="color:red;">' + message + '</p>');
});
});
</script>
</body>
</html>此示例创建了一个 simulateAsyncTask 函数,它返回一个 Promise 对象(通过 deferred.promise() 获得)。外部代码通过 .progress()、.done() 绑定回调,而无法直接调用 resolve 或 reject,这保证了封装性。这正是 jQuery异步编程 中常用的模式。
核心方法:控制状态
Deferred 对象提供了一系列方法来改变其内部状态,从而触发相应的回调。
.resolve() / .resolveWith()
将 Deferred 对象的状态设置为 resolved,并执行所有已绑定的 done 回调。
语法
deferred.resolve( [args] )
deferred.resolveWith( context [, args] )- args:传递给回调函数的参数。
- context:在回调函数内部
this指向的对象。
.reject() / .rejectWith()
将 Deferred 对象的状态设置为 rejected,并执行所有已绑定的 fail 回调。
语法
deferred.reject( [args] )
deferred.rejectWith( context [, args] ).notify() / .notifyWith()
执行所有已绑定的 progress 回调,但不改变 Deferred 对象的最终状态。
语法
deferred.notify( [args] )
deferred.notifyWith( context [, args] ).promise()
返回一个受限制的 Promise 对象。该对象只包含用于绑定回调的方法(如 done、fail、progress、then 等),而不包含修改状态的方法(如 resolve、reject)。这是防止外部代码意外改变内部状态的关键。
语法
deferred.promise( [target] )- target(可选):一个对象,Promise 的方法将被合并到这个对象上。
添加回调:响应状态变化
通过以下方法可以为 Deferred 对象 绑定回调函数,当状态改变时它们会被自动执行。
| 方法 | 触发时机 | 描述 |
|---|---|---|
.done( callbacks ) | 状态变为 resolved | 添加一个或多个成功回调。 |
.fail( callbacks ) | 状态变为 rejected | 添加一个或多个失败回调。 |
.progress( callbacks ) | 每次调用 .notify() | 添加一个或多个进度回调。 |
.then( doneFilter [, failFilter ] [, progressFilter ] ) | 视情况而定 | 将三个类型的回调合并,并可对结果进行链式过滤。 |
.always( callbacks ) | 状态变为 resolved 或 rejected | 添加一个无论成功还是失败都会执行的回调。 |
示例:综合使用各种回调
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Deferred 回调示例</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<button id="randomTask">执行随机结果的任务</button>
<div id="output"></div>
<script>
function randomAsyncTask() {
var deferred = $.Deferred();
var random = Math.random();
setTimeout(function() {
if (random > 0.5) {
deferred.resolve('成功!随机数大于 0.5');
} else {
deferred.reject('失败!随机数小于等于 0.5');
}
}, 1000);
return deferred.promise();
}
$('#randomTask').on('click', function() {
var $out = $('#output');
$out.html('等待结果...');
randomAsyncTask()
.done(function(msg) {
$out.html('<p style="color:green;">成功: ' + msg + '</p>');
})
.fail(function(msg) {
$out.html('<p style="color:red;">失败: ' + msg + '</p>');
})
.always(function() {
$out.append('<p><em>任务已结束(always 回调)</em></p>');
});
});
</script>
</body>
</html>此示例展示了 .done()、.fail() 和 .always() 的用法,清晰体现了 jQuery延迟对象 如何根据异步结果执行不同的逻辑分支。
$.when():管理多个异步任务
$.when() 是一个工具函数,用于协调多个 Deferred 对象(或任何 Promise 对象)的执行。它接受一个或多个 Deferred 对象作为参数,并返回一个新的 Promise 对象。
语法
jQuery.when( deferreds )- 如果传入单个 Deferred,则返回的 Promise 对象直接反映该 Deferred 的状态。
- 如果传入多个 Deferred,则返回的 Promise 对象在所有 Deferred 都被 resolved 时变为 resolved,但只要有一个被 rejected,它就立即变为 rejected。其回调函数接收的参数是各个 Deferred 的 resolve 参数组成的数组。
示例:等待多个异步操作完成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>$.when() 示例</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<button id="multiTask">执行两个并行任务</button>
<div id="result"></div>
<script>
function taskA() {
var d = $.Deferred();
setTimeout(function() { d.resolve('任务 A 完成'); }, 1500);
return d.promise();
}
function taskB() {
var d = $.Deferred();
setTimeout(function() { d.resolve('任务 B 完成'); }, 1000);
return d.promise();
}
$('#multiTask').on('click', function() {
var $result = $('#result');
$result.html('等待所有任务...');
$.when(taskA(), taskB()).done(function(resultA, resultB) {
$result.html('<p>' + resultA + '</p><p>' + resultB + '</p>');
}).fail(function() {
$result.html('<p style="color:red;">至少一个任务失败</p>');
});
});
</script>
</body>
</html>$.when() 使得处理并行异步任务变得异常简单,是 jQuery异步编程 中不可或缺的工具。
将 Ajax 转化为 Promise
从 jQuery 1.5 开始,$.ajax() 返回的对象(jqXHR)实现了 Promise 接口。这意味着可以像操作 Deferred 一样链式调用 .done()、.fail()、.always() 和 .then() 来处理 Ajax 请求结果。
示例:使用 Promise 风格的 Ajax
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ajax 与 Promise 结合</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<button id="loadData">加载数据</button>
<div id="content"></div>
<script>
$('#loadData').on('click', function() {
var $content = $('#content');
$content.html('加载中...');
$.ajax({
url: 'https://api.github.com/repos/jquery/jquery',
dataType: 'json'
})
.done(function(data) {
$content.html('<p>仓库名: ' + data.full_name + '</p><p>星标: ' + data.stargazers_count + '</p>');
})
.fail(function(jqXHR, textStatus) {
$content.html('<p style="color:red;">请求失败: ' + textStatus + '</p>');
})
.always(function() {
$content.append('<p><em>请求结束 (always)</em></p>');
});
});
</script>
</body>
</html>这种写法避免了传统的回调嵌套,使代码更加扁平化和易于维护。
jQuery 4.0 的变化
在 jQuery 4.0.0 中,官方对 Deferred 和 Callbacks 模块 进行了调整。根据 官方博客 的说明:
- slim 构建版本 移除了 Deferred 和 Callbacks 模块,使核心库体积减少约 8k(gzipped)。
- 主要原因是所有支持 jQuery 4.0 的浏览器(除了 IE11)都已原生支持 Promise/A+ 标准。因此,大多数情况下可以使用原生 Promise 替代 Deferred。
- 对于需要支持 IE11 或使用 Deferred 特有功能(如
.notify()进度通知)的场景,建议使用完整版的 jQuery 4.0 或引入 polyfill。
这一变化反映了前端社区向标准化、轻量化发展的趋势。了解 $.Deferred() 的原理依然非常重要,因为它不仅有助于维护旧项目,也加深了对异步编程模型的理解。
版本变更记录
| 版本 | 变更内容 |
|---|---|
| 1.5 | 引入 jQuery.Deferred 模块,并重构 Ajax 模块以返回实现 Promise 接口的 jqXHR 对象。 |
| 1.6 | 增强 .pipe() 方法(后被 .then() 替代),改进链式调用能力。 |
| 1.8 | .then() 方法的行为被标准化,以更接近 Promise/A+ 规范。 |
| 3.0 | 正式弃用 .pipe() 方法,推荐使用 .then()。jqXHR.success()、.error()、.complete() 被弃用,推荐使用 Promise 风格方法。 |
| 4.0 | slim 构建版本 移除 Deferred 和 Callbacks 模块。完整版本保留。推荐在新项目中使用原生 Promise。 |
浏览器兼容性
jQuery.Deferred() 的兼容性与 jQuery 核心库保持一致。对于使用完整 jQuery 4.0(包含 Deferred 模块)的项目,其支持范围如下:
| 浏览器 | 最低支持版本 |
|---|---|
| Chrome | 30+ |
| Edge | 12+ |
| Firefox | 25+ |
| Opera | 18+ |
| Safari | 7+ |
| Chrome Android | 30+ |
| Firefox for Android | 25+ |
| Opera Android | 18+ |
| Safari on iOS | 7+ |
| Samsung Internet | 4.0+ |
| WebView Android | 4.4+ |
| WebView on iOS | 7+ |
注:对于需要支持 IE 6-8 的遗留项目,必须使用 jQuery 1.x 或 2.x 版本,这些版本中 Deferred 功能可用。jQuery 4.0 完整版仍支持 IE11。
掌握 jQuery.Deferred() 是深入理解 jQuery异步编程 的关键一步。它不仅为处理复杂的异步流程提供了优雅的解决方案,其设计思想也深刻影响了后续的 Promise 标准。即便在现代开发中越来越多地使用原生 Promise,Deferred 对象在 jQuery 生态中依然扮演着重要角色,是连接过去与未来异步编程模式的桥梁。
