jQuery Deferred对象提供了done、fail和progress三种回调机制,其中progress回调允许异步任务在尚未完成时持续向外报告状态。常规的notify()方法可以传递任意多个参数,但回调执行时this默认指向Deferred对象本身。如果业务逻辑要求回调里的this代表某个自定义状态对象,notifyWith()提供了更直接的解决方案。

进度通知的基础:notify与notifyWith
先看一个简单的Deferred进度通知例子。下面代码创建Deferred对象,注册progress回调,再通过notify()发送进度消息。回调内部打印this与Deferred对象的比较结果。
var d = $.Deferred();
d.progress(function(info) {
console.log(this === d); // true
console.log('进度信息:' + info);
});
d.notify('第一步完成');
d.notify('第二步完成');
运行后控制台会输出两个true,说明notify()触发的progress回调中,this确实指向Deferred实例。这种默认行为在简单场景下没有问题,但当多个回调需要共享同一份业务数据时,每次都要通过闭包变量或手动绑定上下文,代码会变得啰嗦。
notifyWith()的引入改变了这一点。它接受两个参数:第一个参数是上下文对象,第二个参数是数组,数组中的元素会作为单独参数依次传给回调。例如d.notifyWith(customContext, ['第一步完成']),回调中的this会指向customContext。这种设计允许开发者把状态数据、DOM节点或配置对象直接绑定到回调上,避免额外的闭包或bind调用。
从底层实现看,notifyWith()最终调用的是jQuery Callbacks对象的fireWith()方法。Deferred内部维护了一个进度回调列表,fireWith()负责设置回调执行时的this值并传入参数列表。相比之下,notify()只是将Deferred对象作为上下文,再把可变参数打包成数组转发给notifyWith()。因此notifyWith()可以看作是notify()的底层能力暴露。
notifyWith()如何指定上下文与传参
notifyWith()的函数签名非常直观:deferred.notifyWith(context, args)。其中context是回调执行时的this对象,args是一个数组,数组元素会按顺序展开传递给回调。例如下面的代码创建了一个普通对象progressState,用它作为上下文,回调里通过this直接修改对象属性。
var progressState = { percent: 0, message: '' };
var d = $.Deferred();
d.progress(function(msg) {
this.percent += 10;
this.message = msg;
console.log(this.percent + '% - ' + this.message);
});
d.notifyWith(progressState, ['开始上传']);
d.notifyWith(progressState, ['正在上传']);
d.notifyWith(progressState, ['上传完成']);
运行结果会依次输出10% - 开始上传、20% - 正在上传、30% - 上传完成。可以看到每次notifyWith()调用时传递的上下文对象都是同一个progressState,回调中对this的修改会累积到该对象上。这非常适合用于进度累加、状态跟踪等需求。
如果不需要传递额外参数,第二个参数可以省略或传空数组。例如d.notifyWith(progressState)。但要注意,如果省略第二个参数,回调函数中通过arguments访问到的参数列表为空。如果需要传递多个参数,必须把它们放进数组里,而不是像notify()那样逐个列出。
另一个容易忽略的细节是上下文对象的生命周期。由于每次notifyWith()都应该传入同一个对象实例,如果某次调用传入了新的对象字面量,回调中的this会指向不同对象,之前累积的状态就会丢失。因此通常会在Deferred外部先创建好状态对象,再在多次通知中复用。
实际场景:进度回调里访问自定义状态
假设需要实现一个文件上传进度条,后端陆续返回已上传字节数。我们可以创建一个包含loaded、total和percent属性的state对象,并在setInterval模拟上传过程中通过notifyWith()把state作为上下文传给progress回调。这样回调内部既能通过this获取百分比,也能通过参数拿到具体数值。
function createUploader(totalSize) {
var d = $.Deferred();
var state = {
loaded: 0,
total: totalSize,
percent: 0
};
var timer = setInterval(function() {
state.loaded += 128;
state.percent = Math.floor(state.loaded / state.total * 100);
d.notifyWith(state, [state.loaded, state.total]);
if (state.loaded >= state.total) {
clearInterval(timer);
d.resolveWith(state, ['上传完成']);
}
}, 200);
return d.promise();
}
var uploader = createUploader(1024);
uploader.progress(function(loaded, total) {
// this 指向 state 对象
$('#progress').text(this.percent + '%');
console.log('已上传 ' + loaded + '/' + total);
});
uploader.done(function(msg) {
console.log(this.percent + '% ' + msg);
});
这段代码展示了notifyWith()和resolveWith()配合使用的完整流程。progress回调中的this指向state,所以可以直接读取this.percent更新DOM;done回调中的this同样指向state,能够输出最终百分比。由于state对象在Deferred外部可见,其他模块也可以读取或修改这些状态字段,使数据流更加透明。
相比用闭包变量保存状态,notifyWith()的方式让回调函数保持纯净,不依赖外部作用域,也方便把同一个回调注册到多个Deferred实例上。例如可以定义一个通用的进度渲染函数,只要上下文对象具有percent属性,就能被复用到不同任务中。
另一个典型场景是任务队列。当多个异步任务依次执行时,可以把整个队列的汇总状态对象传给notifyWith(),让每个任务更新自己的进度,同时队列总进度也能被统一监听。
注意事项与替代方案
使用notifyWith()时有几个容易踩坑的地方需要注意。首先,Deferred对象一旦进入resolved或rejected状态,就不要再调用notifyWith()发送进度通知。虽然在jQuery的不同版本中具体行为略有差异,但状态确定后进度回调通常不再触发,继续调用只会造成逻辑混乱。正确的做法是在异步任务最终完成前,先通过notifyWith()发送最后一条进度,再调用resolve()或reject()。
其次,如果某个progress回调中抛出了异常,jQuery Callbacks的fireWith()默认不会中断后续回调的执行,但异常会向上传播到调用notifyWith()的地方。因此如果回调代码依赖外部资源或DOM操作,建议在回调内部使用try/catch包裹关键逻辑,避免一个回调的失败影响整个进度通知流程。
另外,notifyWith()并不能替代状态管理。它只是给回调提供上下文,并不负责维护状态的一致性。如果多个回调同时对上下文对象进行写操作,仍然需要考虑执行顺序和数据竞争。好消息是jQuery会按照回调注册顺序依次调用它们,在同一轮通知中后注册的回调可以看到先注册回调对上下文对象的修改。
作为替代方案,开发者也可以使用闭包捕获this,或者使用$.proxy(fn, context)来预先绑定回调上下文,再配合notify()触发。但闭包方式会引入额外的外层变量,$.proxy方式则需要提前处理回调函数。notifyWith()的优势在于调用时即时指定上下文,逻辑更加内聚,尤其适合上下文对象需要在运行时动态变化的场景。不过如果你的项目已经大量使用ES6的箭头函数,箭头函数本身不具备this绑定,会忽略notifyWith()传入的上下文,此时需要改用普通函数表达式。
从版本兼容角度看,notify、notifyWith以及progress回调在jQuery 1.7版本才正式加入,Deferred本身的done/fail机制从1.5就有了。因此在维护老项目时,需要确认jQuery版本是否支持进度通知API。对于现代项目,虽然原生Promise没有progress能力,但可以结合RxJS等流式库实现类似效果,只是引入额外依赖的代价不一定划算。
总的来说,notifyWith()是一个小而实用的API,它把上下文绑定和进度通知两个动作合并到一起,简化了Deferred在复杂异步流程中的状态传递。理解它的参数规则和执行时机,能帮助开发者在写任务队列、上传下载进度、轮询状态反馈等功能时减少样板代码,让异步逻辑更清晰。
jQuery DeferrednotifyWith进度回调修改时间:2026-10-02 09:18:20