默认情况下,script按文档物理位置顺序串行执行:内联与外链脚本均阻塞html解析,谁先出现谁先执行;加async则下载不阻塞但执行无序,加defer则下载不阻塞且按序于dom解析完成后执行。

行内 script 和外部 script 混合出现时,执行顺序不取决于“内外之分”,而完全由它们在 HTML 文档中出现的**物理位置顺序**决定——谁先写,谁先执行,前提是都不加 async 或 defer。
默认行为:按文档顺序串行阻塞执行
浏览器解析 HTML 是自上而下、单线程进行的。遇到任何 <script></script> 标签(无论内联还是带 src),都会:
- 立即暂停 HTML 解析;
- 对内联脚本:直接编译并执行;
- 对外部脚本:先下载(阻塞后续解析)、再编译执行;
- 执行完毕后,才继续解析下方的 HTML 内容。
例如:
<script>console.log('1. inline');</script>
<script src="b.js"></script>
<script>console.log('2. inline after b.js');</script>
执行顺序一定是:1. inline → b.js 内容 → 2. inline after b.js。中间的 <div id="b"> 虽然写在 <code>b.js 后面,但它的 DOM 节点要等 b.js 执行完才被创建。
关键陷阱:DOM 可用性与依赖错位
混合使用时最常出问题的不是执行顺序本身,而是脚本对 DOM 的访问时机:
- 写在
里的内联脚本,会尝试操作document.body或getElementById,但此时还没开始解析 → 报错或返回null; - 一个外部脚本
a.js定义了函数init(),紧接着的内联脚本却立刻调用init()—— 这没问题,因为a.js已执行完毕; - 但如果把内联脚本写在
a.js前面,就无法调用它,哪怕两者都在里。
async/defer 会彻底改变混合逻辑
一旦给任意一个 script 加了 async 或 defer,它就脱离了“文档顺序执行”的队列:
-
async脚本:下载不阻塞,但执行时机不可控,可能早于、晚于甚至插在两个同步脚本之间; -
defer脚本:下载不阻塞,所有defer脚本会在 DOM 解析完成后、DOMContentLoaded前,严格按书写顺序执行; - 同步脚本(无属性)仍按原顺序执行,且会阻塞解析;
defer和async脚本不会干扰它们的执行时机,但可能在时间线上交错。
混用时建议:要么全同步(明确控制顺序),要么统一用 defer(推荐),避免 async 与同步脚本交叉引发竞态。
真实开发中的稳妥做法
不需要靠“内外混合”来组织逻辑,而应按职责分层:
- 配置类、启动入口等少量代码,用内联
<script></script>放在前; - 业务逻辑、工具函数等,拆成外部文件,统一加
defer; - 绝对避免在
写同步内联脚本去操作尚未生成的 DOM; - 如果必须动态判断加载策略,用
document.createElement('script')插入 —— 它默认表现等同async,不保序。











