type="module"是启用es模块的必要条件,具有defer效果、严格模式、作用域隔离特性;导入路径必须为合法url,不支持裸包名或自动补后缀;动态import()返回promise,可用于按需加载。

module脚本必须用type="module",且默认是defer行为
浏览器只在<script type="module"></script>时才启用ES模块解析,普通type="text/javascript"或无type属性的<script></script>不会触发模块加载。它天然具有defer效果:脚本会按出现顺序下载并执行,且不阻塞HTML解析,也不等待DOMContentLoaded——执行时机等同于defer脚本,但多了模块作用域和import支持。
常见错误:漏写type="module"却用import语句,直接报Uncaught SyntaxError: Cannot use import statement outside a module;或者写了type="module"但路径是相对URL(如./utils.js),却被当成非模块上下文处理——根源还是type没生效。
模块路径必须是有效URL,不能省略协议或扩展名
ES模块导入路径受严格限制:import后跟的字符串必须是合法的URL(相对或绝对),不支持Node.js式的包名裸导入(如import { foo } from 'lodash'),也不支持自动补.js后缀。
-
import { fn } from './helper.js'✅ 显式带.js扩展名 -
import { fn } from '/src/lib/index.mjs'✅ 绝对路径+明确扩展名 -
import { fn } from './helper'❌ 报错:Failed to load module script -
import { fn } from 'lodash'❌ 浏览器无法解析裸包名
开发中若想用裸名,需配合构建工具(如Vite、Webpack)或原生<script type="importmap"></script>声明映射关系。
模块脚本默认是严格模式,且作用域隔离
所有type="module"脚本自动启用严格模式(无需"use strict"),且顶层变量、函数不会泄漏到window上——这是模块系统的基本保障。
容易踩的坑:
- 调试时在控制台直接调用模块内函数失败,因为没挂载到全局,得用
import显式引入或通过export暴露接口 - 多个
type="module"脚本之间不共享顶层作用域,即使同名变量也互不影响 -
this在模块顶层为undefined(严格模式特性),而非window
动态import()可突破静态限制,但返回Promise
静态import只能出现在模块顶层,而import()是函数调用,可在条件分支、循环、事件回调里使用,适合做代码分割或按需加载。
注意点:
- 返回的是
Promise,必须用await或.then()处理结果 - 路径仍需是有效URL,不支持变量拼接(如
import(base + '/mod.js')会报语法错误,需确保字符串字面量 - 多次
import()同一路径,模块只会执行一次,后续返回缓存的exports
例如:const { render } = await import('./renderer.js'); —— 这行能放在click事件里,但不能写成import('./renderer.js').then(...)再链式调用render,除非你确认render已导出且未被重命名。
type="module"时最容易反复卡住的地方。尤其当从传统脚本迁移过来,习惯把逻辑堆在全局,突然发现变量“不见了”,其实是模块机制在起作用,不是bug。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











