You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XMLHttpRequest的readystatechange事件为何在当前脚本结束前触发?

为什么XMLHttpRequest的readystatechange回调会在主脚本结束前执行?

这个问题问到点子上了!我当初第一次碰到这个情况的时候,也跟你一样困惑——按浏览器事件循环的常规逻辑,事件回调明明应该排队等主执行栈空了再跑,怎么这里就提前执行了?其实答案藏在XMLHttpRequest的同步请求模式里。

关键原因:同步XHR会直接阻塞主线程,事件同步触发

当你调用xhr.open()的时候,如果把第三个参数设成了false(也就是显式开启同步模式),那后续的xhr.send()就会彻底卡住主线程——直到整个请求从发送到接收完所有数据的全过程结束,才会继续往下走。

在这个阻塞的过程中,XHR的readyState每发生一次变化,对应的readystatechange回调都会直接在当前的执行栈里同步执行,根本不会被放到事件队列里排队。也就是说,主脚本的执行会被send()打断,先把所有状态变化的回调跑完,才会继续执行console.log("ends")这行代码。

给你补个完整的代码例子,对应你描述的场景:

const xhr = new XMLHttpRequest();
console.log(xhr.readyState); // 输出 0

// 绑定状态变化回调
xhr.addEventListener('readystatechange', function() {
  console.log(this.readyState); // 会依次输出1、2、3、4,全在"ends"之前
});

// 开启同步请求模式(第三个参数是false)
xhr.open('GET', '/your-request-url', false);
xhr.send(); // 这里开始阻塞,直到请求彻底完成
console.log("ends"); // 要等所有回调执行完才会打印

那异步模式下会符合你的预期吗?

当然!如果是默认的异步模式(xhr.open()第三个参数不写或者设为true),那send()就不会阻塞主线程,readystatechange的回调会乖乖进入事件队列,等主脚本里的所有代码(包括console.log("ends"))执行完之后,才会被取出来执行。

比如异步模式的代码:

const xhr = new XMLHttpRequest();
console.log(xhr.readyState); // 输出 0

xhr.addEventListener('readystatechange', function() {
  console.log(this.readyState); // 会在"ends"之后才输出1、2、3、4
});

xhr.open('GET', '/your-request-url'); // 默认异步
xhr.send(); 
console.log("ends"); // 先输出这行

最后提一句:同步XHR尽量别用

这种同步阻塞的特性是早期API设计的遗留产物,现在已经不推荐在主线程里用了——因为它会卡住页面,用户没法点击、滚动,页面也没法渲染,体验特别差。现在大家基本都用fetch()或者异步XHR来处理网络请求啦。


内容的提问来源于stack exchange,提问作者Flia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:34:42