静态导入在解析阶段确定依赖并共享执行上下文,路径固定、活绑定、错误不可捕获;动态导入在运行时触发完整生命周期,路径可计算、返回命名空间对象、支持promise错误处理。

静态导入和动态导入的核心区别在于模块加载的时机与执行上下文的绑定方式:静态导入在模块解析阶段就确定依赖关系并建立执行上下文,而动态导入则把模块加载、实例化和求值推迟到运行时,每次调用都生成独立的执行上下文。
执行上下文的创建时机不同
静态导入的模块在 JavaScript 引擎构建模块图(Module Graph)时即完成链接(Linking)与求值(Evaluation),其执行上下文在模块首次被引用时就已确定,并与导入它的模块共享顶层作用域的初始化顺序。所有静态导入语句必须位于模块顶层,确保上下文可被静态分析。
- 静态导入上下文在编译期“冻结”——路径是字符串字面量,不能含变量或表达式
- 动态导入每次调用 import() 都触发一次完整的模块生命周期:获取 → 解析 → 链接 → 求值,产生新的执行上下文
- 同一模块被多次动态导入,可能触发多次求值(取决于运行时环境是否缓存模块记录),而静态导入始终只求值一次
作用域与绑定行为差异
静态导入创建的是**活绑定(live binding)**:导入的变量名直接映射到源模块的导出绑定,源模块修改值,导入方可见;动态导入返回的是模块命名空间对象(Module Namespace Object),其属性默认为只读访问器,反映的是模块求值完成后的快照状态(ES2022 起支持部分场景下的活绑定更新,但兼容性需谨慎验证)。
- 静态导入:
import { count } from './counter.js'→ count 是对源模块export let count = 0的实时引用 - 动态导入:
const { count } = await import('./counter.js')→ 此处 count 是求值后复制的原始值(若为基本类型),非响应式绑定 - 若需保持响应性,动态导入后应主动监听或通过函数暴露状态(如
module.getCount())
错误处理与上下文隔离性
静态导入失败会导致整个模块求值中止,抛出 TypeError(如模块不存在、语法错误),且错误发生在模块加载早期,无法用 try/catch 捕获;动态导入则包装为 Promise,天然支持运行时错误捕获,其执行上下文与当前调用栈隔离,失败不影响主模块继续执行。
- 静态导入报错:浏览器控制台显示 “Failed to load module”,应用可能白屏
- 动态导入报错:可被
.catch()或try/catch拦截,适合降级策略(如加载备用组件、提示用户重试) - 动态导入的模块即使加载失败,也不会污染当前模块的顶层作用域或执行上下文
适用场景与上下文敏感设计
当模块行为依赖当前执行上下文的状态(如用户权限、设备类型、A/B 实验分组),动态导入更自然——它允许你基于运行时条件构造路径并加载对应实现;静态导入因路径固定,只能配合条件导出(如 export const Component = isMobile ? MobileComp : DesktopComp)来间接适配上下文。
- 典型上下文敏感路径:
import(`./locales/${lang}/messages.js`) - 权限控制示例:
if (user.hasRole('admin')) await import('./admin-dashboard.js') - 注意:动态路径仍需符合模块解析规则,不能是任意字符串(如不能拼接用户输入未经校验的路径)











