动态:模块的依赖关系建立在代码运行阶段
静态:模块的依赖关系建立在代码编译阶段
ES6模块
- 每个文件作为一个模块,每个模块拥有独立的作用域。
- 通过exports导出
//命名导出: exports { a, b }; export const a = 1024; //默认导出: exports default a; (只能导出一个对象)
- 通过import导入,默认导出的变量,导入时可以随意命名,命名导出方式,导入时名称必须一致,可以使用as 重命名。
CommonJS模块
- 最初为服务端设计,node.js版本。
- 每个文件即使一个模块,用于独立的作用域。
- 导出是一个模块向外暴露自己的唯一方式。CommonJS中通过module.exports导出模块中的内容。
- 使用
module.exports
导出一个具体的值或对象(默认导出): -
// 导出一个值 module.exports = 'Hello'; // 导出一个对象 module.exports = { name: 'John', age: 25 };
- 使用
exports
对象导出多个成员: -
// 导出多个成员 exports.name = 'John'; exports.age = 25;
- CommonJS中使用require进行模块的导入。
如果导入的模块是第一次被加载,这时会首先执行该模块,然后导出执行后的内容。 如果模块曾经被加载过,则直接导出第一次加载时执行后的内容。(相当于是一个静态值了)
两种模块的差异
ES6 模块和 CommonJS 模块有很大的差异。
语法上面:CommonJS 模块使用
require()
加载和module.exports
输出,ES6 模块使用import
和export
。用法上面:
require()
是同步加载,后面的代码必须等待这个命令执行完,才会执行。import
命令则是异步加载,或者更准确地说,ES6 模块有一个独立的静态解析阶段,依赖关系的分析是在那个阶段完成的,最底层的模块第一个执行。模块导入时:CommonJS是值拷贝,而ES6则是只读的动态映射。
(在 CommonJS 中,模块导入是值拷贝的,也就是说,导入的模块会被完整地 拷贝到一个新的变量中。这意味着对导入的模块进行修改不会影响原始模块的内容。)
- ES6模块相比CommonJS的优势
- 死代码检测和排除:通过静态分析工具检测出哪些模块没有被调用过。从而在打包时去掉未使用的模块,以减少资源包的体积。
- 模块变量类型检查:JS是动态类型语言,不会在代码执行前检查类型错误,ES6模块属于静态类型模块,有助于确保模块之间的传递的值或者接口类型是正确的。
- 编译器优化:CommonJS无论采用哪种方式,导入的都是一个对象,而ES6模块直接导入变量,减少应用层级,程序效率更高。
CommonJS 模块加载 ES6 模块
CommonJS 的require()
命令不能加载 ES6 模块,会报错,只能使用import()
这个方法加载。
(async () => { await import('./my-app.mjs'); })();
上面代码可以在 CommonJS 模块中运行。
require()
不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层await
命令,导致无法被同步加载。
ES6 模块加载 CommonJS 模块
ES6 模块的import
命令可以加载 CommonJS 模块,但是只能整体加载,不能只加载单一的输出项。
// 正确 import packageMain from 'commonjs-package'; // 报错 import { method } from 'commonjs-package';
这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是module.exports
,是一个对象(CommonJS 导出的对象是通过赋值给 module.exports
或 exports
来实现的,而这种赋值操作是在运行时进行的,并不能在编译时进行静态分析。),无法被静态分析,所以只能整体加载。
静态代码分析:静态代码分析是指在不执行代码的情况下(代码编译阶段),对代码进行解析、分析和处理。静态代码分析能够帮助我们理解代码的结构、依赖关系和执行流程,从而实现诸如语法检查、模块解析、依赖管理等功能。在不执行代码的情况下对其进行检查、优化和转换,从而提高代码的可靠性、可维护性和性能。
加载单一的输出项,可以写成下面这样。
import packageMain from 'commonjs-package'; const { method } = packageMain;