JavaScript 模块化机制
- AMD (Asynchronous Module Definition): 在浏览器中使用,并用 define 函数定义模块;
- CJS (CommonJS): 在 NodeJS 中使用,用 require 和 module.exports 引入和导出模块;
- ESM (ES Modules): JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用 import 和 export 引入和导出模块;
Node 对 ES Modules 支持
Node version 8.5.0 起开始支持 ES Modules 特性, 控制台需要 --experimental-modules
标记
Node version 13.2.0 起开始正式支持 ES Modules 特性, 不需要 --experimental-modules
标记,但是会有警告
Node version v14.0.0 删除警告
Node version 14.17.0 开始稳定支持 ES Modules 特性
如何在在 NodeJS 中使用 ES Modules
- 方式一: 在 package.json 中,增加 “type”: “module” 配置;
├── index.js
└── package.json
import fs from 'fs'
console.log(fs)
- 方式二:在 .mjs 文件可以直接使用 import 和 export;
// index.mjs
import fs from 'fs'
console.log(fs)
- 注意
- 如果 package.json 文件没有将 "type: “module” ,或者 "type: “commonjs”,会使用 CommonJS 加载模块
- 如果使用 .cjs, 也会使用 CommonJS 加载模块
ES Modules 和 CommonJS 区别
ES Modules
在 ESM 中,import 语句用于在解析代码时导入模块依赖的静态链接。文件的依赖关系在编译阶段就确定了。对于 ESM,模块的加载大致分为三步:
1. Construction (解析)
2. Instantiation (实例化、建立链接)
3. Evaluation (执行)
CommonJS
Node 将每个文件都视为独立的模块,它定义了一个 Module 构造函数,代表模块自身:
function Module(id = '', parent) {
Home | This.ID = id;
this.path = path.dirname(id);
this.exports = {};
this.parent = parent;
this.filename = null;
this.loaded = false;
this.children = [];
};
require 函数接收一个代表模块ID或者路径的值作为参数,它返回的是用module.exports
导出的对象。在执行代码模块之前,NodeJs 将使用一个包装器对模块中的代码其进行封装:
(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});
通过这样做,Node.js 实现了以下几点:
- 它保持了顶层的变量(用 var、 const 或 let定义)作用在模块范围内,而不是全局对象;
- 它有助于提供一些看似全局的但实际上是模块特定的变量
- 实现者可以用于从模块中导出值的module 和 exports 对象;
- 包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname ;
在包装器执行之前,模块内的导出内容是不确定的。除此之外,第一次加载的模块会被缓存到 Module._cache中
生命周期
- Resolution (解析)
- Loading (加载)
- Wrapping (私有化)
- Evaluation (执行)
- Caching (缓存)