type="module"脚本默认具备更严格的defer行为,执行时机严格绑定domcontentloaded前一刻,按依赖拓扑顺序串行执行,拥有独立作用域、自动严格模式、禁用document.write、强制cors及支持顶层await。

type="module" 让浏览器按依赖图执行,而不是按 HTML 标签顺序 —— 这是它和 defer 最本质的区别。光加这个属性不写 import/export,它就退化成带作用域隔离的 defer 脚本,起不到依赖管理作用。
为什么 type="module" 脚本总在 DOMContentLoaded 后执行
模块脚本天生具备 defer 行为:下载与 HTML 解析并行,但执行被推迟到整个 DOM 构建完成之后。不过它比传统 defer 更严格:
- 多个
type="module"脚本一定按 HTML 中出现顺序执行,哪怕后写的模块体积小、下载快 - 执行时机不是“DOM 解析完就执行”,而是“所有模块(含递归 import 的子模块)都加载、解析、实例化、执行完毕后”,才触发
DOMContentLoaded - 这意味着一个
import深度为 5 层的模块,可能让DOMContentLoaded显著延迟 —— 它等的是整个模块图闭环,不是单个script标签
type="module" 和 defer 在执行顺序上看起来一样,但底层逻辑完全不同
表面看,两者都“不阻塞解析、按文档顺序执行”,但背后机制天差地别:
-
defer是靠 DOM 树遍历顺序硬保序:浏览器记住script出现位置,等解析完统一按序执行 -
type="module"是靠静态分析import构建依赖图,再按拓扑排序执行:哪怕b.js在 HTML 里写在a.js前面,只要b.js里写了import './a.js',a.js就一定先执行 - 模块内顶层声明(如
const x = 1)不会挂到window上,而defer脚本会 —— 这直接影响跨脚本访问变量的可行性
常见踩坑点:路径、协议、语法缺一不可
加了 type="module" 却报错,大概率卡在这几个硬性条件上:
- 本地双击打开 HTML(
file://协议)时,Chrome/Firefox 直接拒绝加载模块脚本,控制台可能只显示空错误或 CORS 提示 —— 必须用本地服务器(如python3 -m http.server) -
import路径必须带扩展名且显式前缀:import { foo } from './utils.js'✅,import { foo } from './utils'❌,import { foo } from 'utils.js'❌ - 模块脚本里用了
import()动态导入,但该脚本本身没标记type="module",就会报SyntaxError: Cannot use import statement outside a module -
importmap写错位置或类型(比如放在type="module"之后,或写成type="ImportMap"),所有裸导入静默失败
真正关键的不是“有没有加 type="module"”,而是“有没有形成可静态分析的 import/export 链”。一旦断链,浏览器就退回到传统脚本模型,所有依赖顺序保障自动失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











