ES Modules 无子模块初始化抽象,需通过约定实现:导出 init() 函数由父模块显式 await;或用顶层 await 延迟模块就绪;或封装为类/工厂函数解耦加载与初始化,并注意静态 import 副作用、import() 缓存及错误捕获。

ES Modules 本身不提供“子模块初始化”这一层抽象,模块加载即执行,但你可以通过约定和模式,在 import() 动态加载后主动触发初始化逻辑,实现可控的异步初始化流程。
模块导出初始化函数,由调用方显式 await
这是最直接、最可控的方式。子模块不自动运行副作用,而是导出一个 init() 函数,由父模块按需调用并等待:
- 子模块(
./features/chart.js)只做定义,不执行渲染或请求:
chart.js
export async function init(container) {
const data = await fetch('/api/chart-data').then(r => r.json());
renderChart(container, data);
return { status: 'ready', data };
}
export function renderChart(container, data) {
// 实际渲染逻辑
}
- 父模块按需加载并初始化:
main.js
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
async function loadAndInitChart() {
const { init } = await import('./features/chart.js');
const result = await init(document.getElementById('chart-root'));
console.log('图表已就绪:', result);
}
利用顶层 await(仅限模块作用域)延迟模块就绪时机
在支持顶层 await 的环境(如现代浏览器、Node.js v14.8+ 且 "type": "module")中,你可以在子模块顶部写 await,让整个模块的 import() Promise 等待初始化完成后再 resolve:
- 该模块对外暴露的值,只有在初始化完成后才可用:
./features/auth.js
// 模块会等待 token 刷新完成才算“加载完毕”
const token = await fetch('/api/token').then(r => r.text());
export function getUser() {
return fetch('/api/user', { headers: { Authorization: `Bearer ${token}` } });
}
- 注意:顶层 await 会让
import('./auth.js')的 Promise 延迟到初始化结束,适合轻量、确定性高的初始化(如配置获取),不适合含用户交互或重试逻辑的场景。
封装带生命周期的状态模块(推荐用于复杂子模块)
对需要多次初始化/销毁、或有状态依赖的子模块,可封装成类或工厂函数,把加载与初始化解耦:
- 子模块导出构造器,而非立即执行的逻辑:
./plugins/pdf-viewer.js
export class PDFViewer {
constructor(options = {}) {
this.container = options.container;
this.ready = false;
}
async init() {
// 加载 PDF.js 库(可选动态 import)
const { pdfjsLib } = await import('pdfjs-dist/build/pdf.mjs');
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js';
this.pdfDoc = await pdfjsLib.getDocument('/sample.pdf').promise;
this.ready = true;
return this;
}
}
- 使用时清晰分离加载与初始化阶段:
const { PDFViewer } = await import('./plugins/pdf-viewer.js');
const viewer = new PDFViewer({ container: el });
await viewer.init(); // 显式触发异步初始化
避免常见陷阱
-
不要在静态 import 中依赖未就绪的副作用:ESM 静态导入(
import ... from)会在模块解析阶段执行,若子模块内有未 await 的顶层异步操作,其导出值可能为 undefined 或不完整状态; -
重复 import() 不会重复执行顶层代码:浏览器和 Node.js 对同一路径的
import()会复用已解析模块实例,但顶层 await 只执行一次,后续 import() 返回已 resolve 的 Promise; - 错误必须被捕获在调用层:模块加载失败(404)、初始化失败(网络错误、DOM 不存在)都应由父模块用 try/catch 处理,子模块不应静默吞掉初始化异常。

















