内联脚本中document.onreadystatechange为何会提前执行
问题原因
你观察到的onreadystatechange事件“提前触发”、回调中document.readyState值为complete的现象,本质是事件入队时机早于你的事件绑定操作,但事件执行必须等当前同步脚本全部跑完,属于JS事件循环机制下的正常表现,和预期的执行顺序偏差来自对事件派发规则的误解。
核心规则说明
浏览器的DOM文档加载状态按固定顺序流转:
loading:文档正在解析,DOM树未构建完成interactive:文档解析完成,DOM树构建完毕,触发DOMContentLoaded事件,但图片、样式表等子资源仍在加载complete:所有资源加载完成,状态切换到该值后会紧接着派发window.load事件
整个加载过程严格遵循事件循环规则:同步代码执行期间,浏览器不会触发任何事件,所有待派发的事件都会进入宏任务队列排队,等当前同步调用栈清空后才会按入队顺序依次执行。
实际执行流程
你遇到的场景对应执行顺序如下:
- 当浏览器解析到你这段内联同步脚本并开始执行时,DOM解析、所有图片/样式表/iframe等外部资源加载已经全部完成,浏览器已经准备好执行两个后续动作:将
document.readyState更新为complete并派发readystatechange事件、派发window.load事件。 - 因为主线程正在执行你的同步脚本,上述两个事件被直接放入宏任务队列排队,不会立刻执行。
- 你的同步脚本严格按代码编写顺序依次执行:
- 启动
onload计时器 - 启动
onreadystatechange计时器 - 绑定
window.onload回调 - 绑定
document.onreadystatechange回调
- 启动
- 同步代码全部执行完毕,调用栈清空,事件循环开始处理队列内的排队任务:
- 第一个出队的是更早入队的
readystatechange事件,执行回调时打印onreadystatechange、输出document.readyState为complete、结束onreadystatechange计时器。因为计时器是在同步代码中刚启动的,最终计时结果非常短,会让你误以为事件在console.time('onreadystatechange')语句之前就触发了。 - 第二个出队的是
load事件,执行对应回调打印日志、结束onload计时器。
- 第一个出队的是更早入队的
验证方法
你可以在绑定document.onreadystatechange的代码前加一行日志:
console.log('绑定事件时的readyState:', document.readyState)
运行后会发现这行日志输出的状态为interactive,证明你绑定事件时状态还未切换到complete,不存在事件在console.time执行前触发的情况,只是事件排队早于绑定操作,等你绑定完成后才会执行回调。
内容的提问来源于stack exchange,提问作者Jim K
相关产品推荐
相关产品推荐

