【原创】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