type="module"显式声明使脚本按es模块规则解析,支持import/export、默认defer、作用域隔离、严格模式、顶层await,路径须带协议或斜杠且含扩展名,禁用裸导入,受cors限制。

script type="module" 的基本用法
必须用 type="module" 显式声明,否则浏览器不会按 ES Module 规则解析脚本。普通 <script></script> 即使写了 import 也会直接报错:Uncaught SyntaxError: Cannot use import statement outside a module。
加载方式和普通 script 类似,但有关键区别:
-
type="module"脚本默认是defer行为:不阻塞 HTML 解析,且保证执行顺序(按出现顺序) - 不能省略路径协议或斜杠:
src="utils.js"会失败,必须写成src="./utils.js"或src="/js/utils.js"或完整 URL - 模块脚本的顶层
this是undefined,不是window
import 路径必须是有效模块地址
浏览器对 import 的路径校验极严——它不是 Node.js,不支持自动补全、包名解析或 node_modules 查找。所有 import 语句中的路径必须是可直接 HTTP 请求到的文件地址。
常见错误示例:
-
import { foo } from 'lodash'→ 报错:Failed to resolve module specifier "lodash" -
import { bar } from 'utils'→ 报错,除非你用构建工具做了重写,原生不支持 -
import { baz } from './utils'→ 报错,缺少扩展名,必须写./utils.js
正确写法只有一种风格:import { something } from "./math.js" 或 import api from "https://cdn.jsdelivr.net/npm/axios@1.6.7/dist/axios.min.js"。
模块作用域与跨 script 共享变量的问题
每个 type="module" 脚本都是独立模块作用域,彼此不共享顶层变量。即使两个模块都用了 let count = 0,它们互不影响。
想让多个模块“看到”同一个值,只有两种可靠方式:
- 通过显式
export / import建立依赖链(推荐):一个模块export const config = {...},其他模块import { config } from "./config.js" - 挂载到全局对象(不推荐但有时实用):
window.MyLib = { init() { ... } },然后在另一个非模块 script 或模块内读window.MyLib
注意:import 是静态语法,不能出现在 if 或函数里;动态加载要用 import() 函数,返回 Promise。
CORS 和本地文件系统限制
直接双击打开 HTML 文件(file:// 协议)时,Chrome / Edge 会拒绝加载任何 type="module" 脚本,报错:Access to script at 'file:///xxx.js' from origin 'null' has been blocked by CORS policy。
这不是代码写错了,是浏览器安全策略。解决方法只有:
- 用本地服务器启动,比如
npx serve、python3 -m http.server或 VS Code Live Server 插件 - 服务端响应头需包含
Content-Type: application/javascript,否则某些旧版 Safari 会拒绝执行 - CDN 加载远程模块时,对方服务器必须返回
Access-Control-Allow-Origin: *(如 jsDelivr、unpkg 默认支持)
模块加载失败时,控制台错误信息非常具体,通常带行号和请求 URL,优先检查网络面板里的 status 和 response headers。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











