javascript 实现模块池的核心做法是利用 import() 动态导入返回的 promise,结合 map 对象缓存已加载的模块实例。通过维护一个包含引用计数或状态的对象来管理这些模块的生命周期,可以实现按需加载、复用和释放,从而优化首屏性能并降低内存占用。

如何设计模块池的基础结构
基础结构需要解决模块标识与实例的映射问题。我们使用一个全局的 modulePool 对象作为容器,键名为模块路径,值为包含 instance(模块实例)和 refCount(引用计数)的结构体。这种设计确保了同一个模块路径在多次请求时不会重复执行网络请求和解析过程。

const modulePool = new Map();
async function loadModule(modulePath) {
if (modulePool.has(modulePath)) {
const entry = modulePool.get(modulePath);
entry.refCount++;
return entry.instance;
}
try {
const instance = await import(modulePath);
modulePool.set(modulePath, {
instance: instance,
refCount: 1
});
return instance;
} catch (error) {
console.error(`Failed to load module: ${modulePath}`, error);
throw error;
}
}
上述代码展示了核心逻辑:首先检查 modulePool 中是否存在该路径。如果存在,增加引用计数并直接返回实例;如果不存在,调用 import() 进行动态加载,并将结果存入池中。这种方式避免了重复加载,是构建模块池的第一步。
如何实现模块的卸载与资源回收
模块池不仅要能加载,还要能安全地卸载以释放内存。当某个模块不再被使用时,我们需要减少其引用计数。当引用计数归零时,从池中移除该模块,并允许垃圾回收机制回收相关资源。这一步对于长时间运行的单页应用至关重要,防止内存泄漏。

function unloadModule(modulePath) {
if (!modulePool.has(modulePath)) {
console.warn(`Module not found in pool: ${modulePath}`);
return;
}
const entry = modulePool.get(modulePath);
entry.refCount--;
if (entry.refCount {
const modA = await loadModule('./modules/a.js');
console.log('Loaded A');
// 模拟业务逻辑...
unloadModule('./modules/a.js');
console.log('Unloaded A');
})();
这段代码定义了 unloadModule 函数。它首先检查模块是否存在,然后递减引用计数。如果计数降至零或以下,它会调用模块实例中的 cleanup 方法(如果存在),并从 modulePool 中删除该条目。这确保了资源的正确释放。
如何处理动态导入失败的情况
在实际应用中,网络请求可能会失败,或者模块文件可能不存在。处理这些异常情况是模块池健壮性的关键。我们需要在 loadModule 中添加错误处理逻辑,确保失败时不会留下半成品的状态,并且能够向调用者提供明确的反馈。
async function loadModuleWithRetry(modulePath, retries = 3) {
for (let i = 0; i setTimeout(resolve, 1000 * (i + 1)));
}
}
}
这个增强版的加载函数引入了重试机制。它在每次尝试失败后等待一段时间再重试,最多重试三次。如果所有尝试都失败,则抛出带有详细信息的错误。这提高了模块加载的成功率,特别是在网络不稳定的环境下。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











