【原创】CommonJS 模块

参考:

http://nodejs.cn/api/modules.html (nodejs官方教程)

 

常识性知识

nodejs的模块有2种类型:commonjs模块和es模块;

不可以 require() 具有 .mjs 扩展名的文件。 试图这样做会抛出错误。 .mjs 扩展名是保留给 ECMAScript 模块,无法通过 require() 加载。

 

模块封装器

在执行模块代码之前,Node.js 会使用一个如下的函数封装器将其封装

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
  • 它保持了顶层的变量(用 var、 const 或 let 定义)作用在模块范围内,而不是全局对象。
  • 它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
    • 实现者可以用于从模块中导出值的 module 和 exports 对象。
    • 包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。

  

模块加载的顺序

没有路径符号前缀时

当没有以 '/'、 './' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录

加载顺序为

//1,核心模块

//2,node_modules目录

先找当前文件夹下的node_modules文件夹下的module-name文件夹下的package.json 文件指定的main字段文件路径。
## 如果第一种情况没有找到
找当前文件夹下的node_modules文件夹下的module.js 文件
## 如果第二种情况没有找到
找当前文件夹下的node_modules文件夹下的module文件夹下的index.js 文件
## 如果第三种情况没有找到
找的上一级node_modules文件夹,查找顺序与上面一样。

//3,全局目录加载

如果 NODE_PATH 环境变量被设为一个以冒号分割的绝对路径列表,则当在其他地方找不到模块时 Node.js 会搜索这些路径。 此外,Node.js 还会搜索以下的全局目录列表: 1: $HOME/.node_modules 2: $HOME/.node_libraries 3: $PREFIX/lib/node 其中 $HOME 是用户的主目录, $PREFIX 是 Node.js 里配置的 node_prefix。 强烈建议将所有的依赖放在本地的 node_modules 目录。 这样将会更快地加载,且更可靠。

有'/'、 './' 或 '../' 前缀时

以 '/' 为前缀的模块是文件的绝对路径。 例如, require('/home/marco/foo.js') 会加载 /home/marco/foo.js 文件。
以 './' 为前缀的模块是相对于调用 require() 的文件的。 必须在同一目录下
以 '../' 为前缀的模块是相对于调用 require() 的文件的。 必须在上一目录下

加载顺序为

文件
module-name.js
module-name.node

目录
module-name/package.json的main属性
module-name/index.js
module-name/index.node

如果给定的路径不存在,则 require() 会抛出一个 code 属性为 'MODULE_NOT_FOUND' 的 Error。

 

模块分类

核心模块

Node.js 有些模块会被编译成二进制。 这些模块别的地方有更详细的描述。

核心模块定义在 Node.js 源代码的 lib/ 目录下。

require() 总是会优先加载核心模块。 例如, require('http') 始终返回内置的 HTTP 模块,即使有同名文件。

 

文件模块

其实模块都是文件;

按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、 .json 或 .node 拓展名再加载。

 

目录作为模块

如果存在同名文件,会优先使用同名文件;

require('./dir-module') 传入目录名,解析顺序是

//在该目录下查找package.json文件的main属性对应的模块
package.json文件下的main属性

//在该目录下查找index.js文件
./some-library/index.js

//在目录下查找index.node文件
./some-library/index.node

 

node_modules 目录下的模块

如果传递给 require() 的模块标识符不是一个核心模块,也没有以 '/' 、 '../' 或 './' 开头,则 Node.js 会从当前模块的父目录开始,尝试从它的 /node_modules 目录里加载模块。

如果还是没有找到,则移动到再上一层父目录,直到文件系统的根目录。

例子,如果在 '/home/ry/projects/foo.js' 文件里调用了 require('bar.js'),则 Node.js 会按以下顺序查找:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

这使得程序本地化它们的依赖,避免它们产生冲突。

通过在模块名后包含一个路径后缀,可以请求特定的文件或分布式的子模块。 例如, require('example-module/path/to/file') 会把 path/to/file 解析成相对于 example-module 的位置。 后缀路径同样遵循模块的解析语法。

 

循环加载

module首次加载,就会生成副本,循环使用时会返回 module 的 exports 对象的 未完成的副本

a.js

console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');

b.js

console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');

main.js

console.log('main 开始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);

当 main.js 加载 a.js 时, a.js 又加载 b.js。 此时, b.js 会尝试去加载 a.js。 为了防止无限的循环,会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。

当 main.js 加载这两个模块时,它们都已经完成加载。 因此,该程序的输出会是:

$ node main.js
main 开始
a 开始
b 开始
在 b 中,a.done = false
b 结束
在 a 中,b.done = true
a 结束
在 main 中,a.done=true,b.done=true

 

posted @ 2021-03-15 16:00  小匡程序员  阅读(113)  评论(0编辑  收藏  举报