JavaScript 在 HTML 页面加载时的执行顺序详解
了解 JavaScript 在 HTML 页面中的执行顺序对于避免变量未定义错误和保证功能正常至关重要。本文梳理了 JS 的几种引入方式及其触发时机,重点解析了默认同步执行模式下的作用域规则,并通过对比 IE 与现代浏览器的行为差异,介绍了使用 defer 属性和现代事件监听机制来优化和控制脚本加载顺序的最佳实践。
JavaScript 的嵌入方式与执行条件
在 HTML 文档中嵌入 JavaScript 主要有以下几种形式,它们的执行时机各不相同:
- 行内脚本:将代码直接置于
<script>和</script>标签之间,或由src属性引入外部文件。这是最常见的加载方式,随 HTML 解析过程同步执行。 - 事件处理器:如
<p onclick="alert('点击')">或 URL 协议javascript:alert(...)。这类代码不会在页面加载时自动运行,必须等待用户交互(如点击)才会触发。 - Ajax 动态获取:通过 XMLHttpRequest 或 Fetch 异步获取代码后手动执行。执行时间完全由代码逻辑控制,不依赖文档流位置。
页面加载时的默认执行顺序
当不包含特殊属性时,浏览器会暂停 HTML 解析,从上到下依次处理遇到的每个 <script> 标记。其核心规则如下:
- 顺序执行:脚本按照它们在 HTML 文件中出现的先后顺序执行。前一个脚本必须完全执行完毕(包括其中的同步 AJAX 请求),浏览器才会继续解析后面的 HTML 内容。
- 全局作用域:脚本中定义的变量和函数属于全局作用域,后续的脚本块可以访问它们。但需注意,变量必须先声明才能被正确使用,否则获取值为
undefined。 - 函数声明提升:在同一脚本块内,函数声明会被提升到顶部,因此可以在定义之前调用。但如果函数调用位于第一个脚本块,而定义位于第二个脚本块,则会报错,因为此时该函数尚未被解析。
<script type="text/javascript">
// 同一脚本内,先调用后定义是允许的
function callLater() {
alert(1);
}
callLater();
</script>
<script type="text/javascript">
// 跨脚本块:此处调用会失败,因为前面的脚本未定义此函数
// anotherFunction();
</script>
document.write 的动态解析风险
使用 document.write() 插入新脚本是一种激进的操作,它会中断当前解析流,立即执行插入的代码。这在处理外部资源加载时极易引发跨浏览器兼容性问题。
考虑以下场景:在一个脚本中使用 document.write 动态写入另一个包含变量定义的 JS 文件,随后立即读取该变量。
<script type="text/javascript">
document.write('<script src="test.js"></script>');
document.write('<script>alert(tmpStr);</script>');
</script>
在这种写法中,IE 等旧版浏览器可能无法确保 test.js 加载完成后再执行紧随其后的 alert,导致变量未定义。而在 Firefox 和 Opera 中,解析器往往能正确等待资源加载。这种不一致性源于浏览器对“同步脚本执行”与“DOM 构建”优先级的不同实现策略。
解决方案:为了避免此类竞态条件,建议拆分脚本块。让第一个脚本块负责插入外部资源标记并结束解析,利用浏览器继续构建 DOM 的特性,等待网络请求完成后,再由后续的自然出现的脚本块去依赖这些资源。
第1步:拆分脚本块以规避解析竞争
目的:确保外部资源加载期间,主文档解析流程不被错误中断,同时保证依赖变量的代码在其之后执行。
操作:
将上述合并的 document.write 拆分为两个独立的 <script> 块。第一块写入外部 JS 引用并终止;第二块开始执行依赖于外部 JS 的逻辑。
<script type="text/javascript">
// 仅负责插入引用,不再进行后续依赖操作
document.write('<script src="test.js"></script>');
</script>
<script type="text/javascript">
// 此时浏览器通常已准备就绪或正在加载 test.js
// 若仍需严格同步,应考虑使用 defer 或 onload
alert(document.readyState);
</script>
改变执行顺序的标准方法
除了调整代码结构,HTML 规范也提供了专门的属性来干预脚本的执行时序。
第2步:使用 defer 属性延迟执行
目的:让脚本在下载期间并行进行,直到文档解析完成后才开始执行。这解决了传统 script 阻塞渲染的问题,同时保留了脚本的执行顺序。
参数:
在 <script> 标签中添加 defer 属性:<script src="app.js" defer>。
效果:
带有 defer 属性的脚本会按照它们在 HTML 中出现的顺序执行,且全部在 DOMContentLoaded 事件之前运行。这是处理大型应用入口脚本的现代推荐做法。
第3步:注册 window.onload 事件监听
目的:确保在 HTML 文档和图片等资源完全加载完毕后执行特定初始化逻辑。
注意:
直接赋值 window.onload = fn 会覆盖之前的 handler。推荐使用 addEventListener 来挂载多个回调:
window.addEventListener('load', function() {
console.log('所有资源加载完成');
});
第4步:理解 async 与 defer 的区别
目的:区分两种异步加载方式的适用场景。
差异:async 属性用于下载完成后立即执行,不保证执行顺序,适合互不依赖的独立脚本(如统计代码)。而 defer 不仅异步下载,还保持执行顺序,并在解析完成后统一执行,适合有依赖关系的业务逻辑代码。
以上就是 JavaScript 代码在页面加载时的执行顺序详细内容,更多关于 JavaScript 加载优化的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









