Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
// 导出 addX module.exports.addX = 1; // 导入模块 example = require('./example.js'); console.log(example.addX)
commonjs 模块规范
特点
- 所有代码运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
- CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
module对象
Node内部提供一个 Module
构建函数。所有模块都是Module
的实例。每个模块内部,都有一个module
对象,代表当前模块。它有以下属性。
module.id
模块的识别符,通常是带有绝对路径的模块文件名。
module.filename
模块的文件名,带有绝对路径。
module.loaded
返回一个布尔值,表示模块是否已经完成加载。
module.parent
返回一个对象,表示调用该模块的模块。
if (!module.parent) { // ran with `node something.js` app.listen(8088, function() { console.log('app listening on port 8088'); }) } else { // used with `require('/.something.js')` module.exports = app; }
module.children
返回一个数组,表示该模块要用到的其他模块。
module.exports
表示模块对外输出的值。
AMD 模块规范
CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。
由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。
AMD规范使用define方法定义模块,下面就是一个例子:
define(['package/lib'], function(lib){ function foo(){ lib.log('hello world!'); } return { foo: foo }; });
AMD规范允许输出的模块兼容CommonJS规范,这时define
方法需要写成下面这样:
define(function (require, exports, module){ var someModule = require("someModule"); var anotherModule = require("anotherModule"); someModule.doTehAwesome(); anotherModule.doMoarAwesome(); exports.asplode = function (){ someModule.doTehAwesome(); anotherModule.doMoarAwesome(); }; });
ESM 模块规范
「 ESM 」 全称 ECMAScript modules,基本主流的浏览器版本都以已经支持。主要用于浏览器原生支持模块化,<script src="index.js" type="module"></script>
。
ESM 使用实时绑定逻辑,两个模块都指向内存中的相同位置。这意味着,当导出模块更改值时,该更改将显示在导入模块中。
这不同于 CommonJS 模块,在 CommonJS 中,整个导出对象在导出时被复制。这意味着 CommonJS 模块导出的任何值(例如数字)都是副本,如果 CommonJS 模块导出模块以后更改了该值,则导入模块将看不到该更改。
ESM 如何工作
- 浏览器会构建一个依赖关系图。
- 通过指定一个入口文件(
<script src="index.js" type="module"></script>
),然后从这个文件开始,通过其中的import
语句,查找其他代码。
- 通过指定的文件路径, 浏览器就找到了目标代码文件。 但是浏览器并不能直接使用这些文件,它需要解析所有这些文件,以将它们转换为称为模块记录的数据结构。
- 将
模块记录
转换为模块实例
。模块实例
, 实际上是 「代码
」(指令列表)与「状态
」(所有变量的值)的组合。
ESM 动态路径
因为 ESM 的特性,正常情况下是不能使用变量作为路径
// commonjs 其实是同步的,会挂起主线程去加载文件, // 因为 commonjs 主要用于 node,从本地磁盘加载资源是很快的。 require(`${publicPath}/count.js`); // 因为 ESM 规范是先静默分析资源去下载,所以其实在这里时变量还没有执行 import count from `${publicPath}/count.js`; // 但是有时候又希望有这样功能,这个时候我们可以使用import动态导入功能,会启动一个新的加载过程。 import(`${path}/count.js`)
node 中使用
- 碰到这种兼容性的,上 babel 就 OK
- 社区(Node 14)尝试解决此问题的一种方法是使用
.mjs
扩展。使用该扩展名告诉Node,“此文件是一个模块”。
总结
名称 | 导入关键词 | 导出关键词 | 规范来源&备注 |
CommonJS、CJS | require('path') |
module.exports |
NodeJS、同步、加载本地资源同步很快的然后马上执行 |
ESM、MJS | import a from 'path' 、import() |
export 、export default |
浏览器、异步、因为网络因素不可靠会在所有资源都加载完成才执行 |
module.exports.foo = 'bar'
命名导出module.exports = 'baz'
默认导出。
export const sum = (x, y) => x + y;
命名导出export default (x, y) => x + y;
默认导出