ES Module 的 import 导入和 import () 动态导入介绍
一、ES Module 简介
ES Module 是 JavaScript 官方提供的标准化模块系统,它的出现解决了长期以来 JavaScript 在模块管理方面的混乱局面。通过 ES Module,开发者可以更加方便地组织和复用代码,提高代码的可维护性和可扩展性。
二、import 导入
(一)基本语法
import 默认导出
在一个模块中,可以使用export default
来定义默认导出。例如,在myModule.js
文件中:
// myModule.jsconst myFunction = () => {console.log("这是一个默认导出的函数");
};export default myFunction;
在另一个模块中导入这个默认导出:
// main.jsimport myFunction from "./myModule.js";myFunction(); // 输出:这是一个默认导出的函数
import 具名导出
模块中也可以使用具名导出,将多个变量、函数或类分别导出。例如:
// mathModule.jsexport const add = (a, b) => a + b;export const subtract = (a, b) => a - b;
在其他模块中导入具名导出:
// main.jsimport { add, subtract } from "./mathModule.js";console.log(add(2, 3)); // 输出:5console.log(subtract(5, 3)); // 输出:2
也可以给 import 的具名导出起别名:
// main.jsimport { add as sum, subtract as difference } from "./mathModule.js";console.log(sum(2, 3)); // 输出:5console.log(difference(5, 3)); // 输出:2
混合导入
一个模块可以同时有默认导出和具名导出,在导入时也可以混合使用:
// myModule.jsconst myDefaultFunction = () => {console.log("这是默认导出的函数");
};export const myVariable = 42;export default myDefaultFunction;
// main.jsimport myDefaultFunction, { myVariable } from "./myModule.js";myDefaultFunction(); // 输出:这是默认导出的函数console.log(myVariable); // 输出:42
(二)import 的特点
静态性:import
语句在编译阶段就会被解析,这意味着它不能出现在运行时才执行的逻辑中,比如if
语句块内。这使得 JavaScript 引擎可以在代码执行前对模块依赖进行分析和优化。
提升:import
语句会被提升到模块的顶部,即使在代码中它出现在其他语句之后,也会先于其他语句执行。
三、import () 动态导入
(一)基本语法
import()
是 ES2020 引入的动态导入语法,它返回一个Promise
。这使得我们可以在运行时根据条件动态地加载模块。例如:
// main.jsconst condition = true;if (condition) {import("./myModule.js").then((module) => {module.default(); // 假设 myModule.js 有默认导出}).catch((error) => {console.error("加载模块失败", error);});
}
如果模块是具名导出,可以这样使用:
// main.jsimport("./mathModule.js").then((module) => {console.log(module.add(2, 3)); // 假设 mathModule.js 有具名导出 add 函数}).catch((error) => {console.error("加载模块失败", error);});
(二)支持导入 CommonJS
在 Node.js 环境中,import()
语法还支持导入 CommonJS 模块。CommonJS 是一种广泛使用的 JavaScript 模块规范,尤其是在 Node.js 应用中。当使用import()
导入 CommonJS 模块时,需要注意以下几点:
转换规则
CommonJS特性 | ESM转换表现 |
---|---|
module.exports | 成为默认导出default属性 |
exports.xxx | 转换为具名导出属性 |
exports.default | 不会特殊处理 |
动态导出 | 可能无法正确识别 |
const cjsModule = await import('./legacy-module.cjs');
console.log(cjsModule.default); // 默认导出
console.log(cjsModule.namedExport); // 具名导出
默认导出与命名导出:CommonJS 模块只有一个exports
对象用于导出内容,通过import()
导入时,默认导出的是整个exports
对象。例如,有一个 CommonJS 模块commonModule.js
:
// commonModule.js(CommonJS模块)const myValue = 10;exports.myValue = myValue;exports.anotherFunction = () => {console.log("这是CommonJS模块中的另一个函数");
};
在 ES Module 中使用import()
导入该模块:
// main.jsimport("./commonModule.js").then((module) => {console.log(module.myValue); // 输出:10module.anotherFunction(); // 输出:这是CommonJS模块中的另一个函数}).catch((error) => {console.error("加载模块失败", error);});
兼容性:虽然import()
支持导入 CommonJS 模块,但在不同的运行环境中,其兼容性可能有所不同。在 Node.js 中,从 Node.js 13.2.0 版本开始原生支持通过import()
导入 CommonJS 模块。在浏览器环境中,情况相对复杂,一些现代浏览器可能对导入 CommonJS 模块的支持并不完善,这时候可能需要借助工具如 Babel 和 Webpack 来进行处理,将 CommonJS 模块转换为 ES Module 格式,以确保代码在各种环境中都能正常运行。
(三)import () 的优势
代码拆分:在大型应用中,通过import()
可以实现代码的按需加载,将应用的代码拆分成多个小块,只有在需要的时候才加载相应的模块,从而提高应用的初始加载性能。
条件加载:可以根据运行时的条件来决定加载哪个模块,增加了代码的灵活性。例如,根据用户的语言偏好加载不同语言的翻译模块。
(四)import() 导入如何清除缓存
在使用import()动态导入模块时,缓存机制可能会导致加载旧版本的模块内容。根据模块类型不同,清除缓存的方式也有所差异。
导入 ES Module :ES Module 的缓存机制相对严格,要确保每次都加载最新文件,可在每次调用import()时,对文件地址添加不同的查询参数,以此强制浏览器或 Node.js 重新请求该模块。例如:
const modulePath = './myModule.js';
const uniqueModulePath = `${modulePath}?v=${Date.now()}`;
import(uniqueModulePath).then((module) => {// 使用模块}).catch((error) => {console.error('加载模块失败', error);});
上述代码通过Date.now()生成一个随时间变化的唯一值作为查询参数,确保每次请求的模块路径不同,从而避免缓存。
导入 Common JS 模块:在 Node.js 环境中,CommonJS 模块的缓存管理与 ES Module 不同。若要手动清除缓存,需借助require方法。在 ES Module 中,本身没有直接的require方法,但可通过以下方式获取:
import { createRequire } from "module";
const require = createRequire(import.meta.url);
获取require方法后,就能像在 Common JS 模块中一样清除缓存。假设要动态加载并确保每次获取最新的 Common JS 模块legacyModule.cjs:
const modulePath = './legacyModule.cjs';
delete require.cache[require.resolve(modulePath)];const module = require(modulePath);
// 也可以使用import()
import(modulePath).then((module)=>{console.log(module)
})
这段代码先通过require.resolve(modulePath)获取模块在缓存中的路径,再从require.cache中删除该路径对应的缓存,确保下次require / import 时重新加载模块。
通用加载方案:若不确定要加载的模块是 ES Module 还是 Common JS 模块,可构建一个通用的加载函数,综合上述两种方式来处理缓存问题。以下是一个示例:
import path from "path";
import { pathToFileURL } from "url";
import { createRequire } from "module";
const require = createRequire(import.meta.url);function loadFile(filePath) {delete require.cache[require.resolve(filePath)];const fileUrl = pathToFileURL(path.resolve(filePath)).href;const urlWithCacheBuster = `${fileUrl}?v=${Date.now()}`;return import(urlWithCacheBuster).then((module) => {return module.default || module;}).catch((err) => {console.error(`文件加载失败 ${modulePath}:`, err);});
}export { loadFile };
此函数loadFile 首先手动清除 Common JS 模块的缓存,再通过添加查询参数避免ES Module缓存;最后通过import 加载文件。这样无论模块是何种类型,都能尽可能确保加载到最新内容。
四、import 和 import () 的区别
静态与动态:import
是静态导入,在编译阶段确定依赖关系;import()
是动态导入,在运行时确定依赖关系。
使用场景:import
适用于那些在模块初始化时就需要加载的依赖;import()
更适合用于代码拆分和条件加载的场景。
语法形式:import
是声明式语法,而import()
是函数调用语法,返回一个Promise
。
五、总结
ES Module 的import
导入和import()
动态导入为开发者提供了强大的模块管理能力。import
的静态特性使得代码的依赖关系更加清晰,便于优化;import()
的动态特性则为代码的灵活性和性能优化提供了更多可能。在实际开发中,我们应根据具体的需求选择合适的导入方式,以构建高效、可维护的 JavaScript 应用。