模块脚本默认defer,不阻塞HTML解析,执行时机可控、顺序确定、作用域隔离;需满足CORS,内联模块支持顶层await,旧全局变量需显式挂载。

模块脚本不会阻塞 HTML 解析,这一点和普通脚本截然不同。
普通 <script> 标签默认同步执行:浏览器一遇到它,立刻暂停 DOM 构建,下载、解析、执行脚本,完成后才继续往下解析 HTML。哪怕只是 console.log(1),也会卡住整个页面渲染流程。
而 <script type="module"> 从设计上就规避了这种阻塞——它默认具备 defer 行为:下载与 HTML 解析并行进行,执行则严格推迟到整个文档解析完成之后、DOMContentLoaded 触发之前。
这带来三个关键差异:
立即学习“前端免费学习笔记(深入)”;
执行时机更可控
- 普通脚本:执行点不可控,可能在 DOM 还没出现目标元素时就运行
- 模块脚本:天然等 DOM 就绪,无需手动监听
DOMContentLoaded,写document.getElementById('main')安全可靠
执行顺序更确定
- 普通脚本(无属性):按标签顺序串行执行,但受网络延迟影响明显
- 模块脚本:即使跨 CDN 加载,也严格按 HTML 中的书写顺序执行,由模块图决定,不是靠 DOM 顺序模拟
作用域与上下文更隔离
- 普通脚本:顶层声明自动挂到
window上,容易污染全局、引发命名冲突 - 模块脚本:顶层
const app = {}不进window,document.currentScript永远是null,无法靠它取当前<script>标签
需要特别注意的硬性限制:
- 模块脚本跨域必须满足 CORS,比如引入
https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/index.js,服务端没返回Access-Control-Allow-Origin,脚本直接静默失败 - 内联模块脚本(如
<script type="module">import {foo} from './a.js';</script>)支持顶层await,但不能依赖document.currentScript或隐式全局变量 - 若旧逻辑靠全局配置传递参数(如先
<script>window.API_BASE = '/v1'</script>,再<script type="module">fetch(window.API_BASE)</script>),必须显式挂载并确保顺序,否则读不到
不复杂但容易忽略



















