1. 概念

在nodejs中,由于有且仅有一个入口文件(启动文件),而开发一个应用肯定会涉及到多个文件配合,因此,nodejs对模块化的需求比浏览器端要大的多

示例图片

由于nodejs刚刚发布的时候,前端没有统一的、官方的模块化规范,因此,它选择使用社区提供的CommonJS作为模块化规范

在学习CommonJS之前,首先认识两个重要的概念:模块的导出模块的导入

2. 模块的导出

要理解模块的导出,首先要理解模块的含义

什么是模块?

模块就是一个JS文件,它实现了一部分功能,并隐藏自己的内部实现,同时提供了一些接口供其他模块使用

模块有两个核心要素:隐藏暴露

隐藏的,是自己内部的实现

暴露的,是希望外部使用的接口

任何一个正常的模块化标准,都应该默认隐藏模块中的所有实现,而通过一些语法或api调用来暴露接口

暴露接口的过程即模块的导出:

示例图片

3. 模块的导入

当需要使用一个模块时,使用的是该模块暴露的部分(导出的部分),隐藏的部分是永远无法使用的。
当通过某种语法或api去使用一个模块时,这个过程叫做模块的导入

4. CommonJS规范

CommonJS使用 exports 导出模块,require 导入模块

注意: 导入的路径只能是 ./ 或者 ../ 开头

具体规范如下:

  1. 如果一个JS文件中存在 exportsrequire,该JS文件是一个模块
  2. 模块内的所有代码均为隐藏代码,包括全局变量、全局函数,这些全局的内容均不应该对全局变量造成任何污染
  3. 如果一个模块需要暴露一些API提供给外部使用,需要通过 exports 导出,exports 是一个空的对象,你可以为该对象添加任何需要导出的内容
  4. 如果一个模块需要导入其他模块,通过 require 实现,require 是一个函数,传入模块的路径即可返回该模块导出的整个内容

5. nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到 require 函数时才会加载并执行模块

  2. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。

    1
    2
    3
    (function(){
    //模块中的代码
    })()
  3. 为了保证顺利的导出模块内容,nodejs做了以下处理

    1. 在模块开始执行前,初始化一个值 module.exports = {}
    2. module.exports 即模块的导出值
    3. 为了方便开发者便捷的导出,nodejs在初始化完 module.exports后,又声明了一个变量 exports = module.exports
    1
    2
    3
    4
    5
    6
    (function(module){
    module.exports = {};
    var exports = module.exports;
    //模块中的代码
    return module.exports;
    })()
  4. 为了避免反复加载同一个模块,nodejs默认开启了模块缓存,如果加载的模块已经被加载过了,则会自动使用之前的导出结果

6. 示例代码

功能:实现控制台逐字输出文本;
所有文件同级

6-1. 配置模块【config.js】

1
2
3
4
module.exports = {
wordDuration: 300, // 打印每个字的时间间隔
text: `如果能不长大该有多好,可时光在身后挡住退路。 --未闻花名`, //打印的文本
};

6-2. 延时模块【delay.js】

1
2
3
4
5
6
7
8
9
10
11
/**
* 该函数返回一个Promise,它会等待指定的毫秒数,时间到达后该函数完成
* @param {number} ms 毫秒数
* @returns {Promise}
*/

function delay(ms){
return new Promise((resolve) => setTimeout(resolve, ms));
};

module.exports = delay;

6-3. 输出模块【print.js】

1
2
3
4
5
6
7
8
9
10
11
12
13
const config = require('./config');
/**
* 该函数会做以下两件事:
* 1. console.clear() 清空控制台
* 2. 读取config.js中的text配置,打印开始位置到index位置的字符
* @param {number} index
*/
function print(index) {
console.clear();
console.log(config.text.substring(0,index+1));
};

module.exports = print;

6-4. 入口文件【main.js】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const config = require('./config');
const delay = require('./delay');
const print = require('./print');
/**
* 运行该函数,会逐字打印config.js中的文本
* 每个字之间的间隔在config.js已有配置
*/

async function run() {
let index = 0;
while(index < config.text.length){
print(index)
await delay(config.wordDuration);
index++;
};
};

run();

__END__