动态 import 和 top-level await 分别适合什么场景?
简化版
动态 import() 可以在运行时按需加载模块,返回一个 Promise,常用于路由级代码分割、条件加载、重型依赖延迟加载。top-level await 允许在 ES Module 顶层直接等待异步结果。
动态导入强调“什么时候加载”,top-level await 强调“模块初始化可以等待异步依赖”。它们都可能影响加载链路,需要谨慎用于首屏关键路径。
详细版
动态导入:
button.addEventListener("click", async () => {
const { openEditor } = await import("./editor.js");
openEditor();
});
top-level await:
const config = await fetch("/config.json").then(res => res.json());
export { config };
如果某个模块使用 top-level await,依赖它的模块会等待它初始化完成。用于配置、WASM 初始化等场景时很方便,但放在核心依赖上可能阻塞整条模块图。
完整版教学
一、先区分两种能力
动态 import() 是表达式,可以出现在函数、条件分支、事件回调里。
top-level await 是模块顶层能力,让模块初始化阶段可以等待异步操作。
| 能力 | 关注点 | 返回或影响 |
|---|---|---|
import() | 按需加载模块 | Promise |
| top-level await | 模块初始化等待 | 阻塞依赖方执行 |
一个控制加载时机,一个控制模块初始化节奏,别把它们混成同一个概念。
二、动态 import 用于按需加载
async function openChart() {
const { renderChart } = await import("./chart.js");
renderChart();
}
这会给打包工具代码分割的机会,把不需要首屏加载的代码拆到单独 chunk。
比如一个管理后台首屏 300KB,图表编辑器 500KB,动态导入可以让 500KB 延后到用户真正点击时再加载。
三、动态 import 适合条件加载
if (locale === "zh-CN") {
await import("./locale/zh-CN.js");
} else {
await import("./locale/en-US.js");
}
它也适合只在浏览器环境加载某些库。
if (typeof window !== "undefined") {
await import("./browser-only.js");
}
四、top-level await 改变模块执行顺序
// config.js
export const config = await loadConfig();
依赖 config.js 的模块会等待它完成。
main.js -> config.js -> await loadConfig()
main.js waits
这让异步初始化更自然,但也可能让启动链路变长。
五、循环依赖和阻塞风险
模块图里若存在复杂循环依赖,再叠加 top-level await,理解成本会明显上升。
a.js awaits b.js
b.js imports a.js
这种结构容易让初始化顺序难以推理。工程上应避免在基础模块、公共工具模块里随意放 top-level await。
六、打包和运行环境注意
动态导入通常能被现代打包工具识别为分包边界,但变量路径太动态会影响静态分析。
import(`./pages/${name}.js`);
这类写法可能生成上下文模块,把一批候选文件都打进去。top-level await 则需要目标运行环境和构建链支持 ES Module 语义。
七、常见误区与追问
- 误区:动态 import 一定提升性能。 如果拆分过细或命中首屏路径,可能增加请求和等待。
- 误区:import() 和静态 import 只有写法不同。 前者是运行时加载表达式,后者是静态依赖声明。
- 误区:top-level await 只影响当前模块。 依赖它的模块也会等待其初始化完成。
- 误区:所有模块都适合顶层 await 配置。 基础模块使用它会放大阻塞范围。
- 追问:动态 import 返回什么? 返回解析为模块命名空间对象的 Promise。
- 追问:变量路径为什么要谨慎? 它可能降低打包工具静态分析能力,导致多打包或分包不清晰。
八、加强记忆
记忆方式:
import() -> load later
top-level await -> initialize later
static import -> declare dependency early
面试回答时先分清加载时机和初始化等待,再讲代码分割、条件加载、配置初始化,最后补充首屏阻塞和模块图复杂度风险。