介绍
Node.js的模块设计,每一个件都作为一个模块。模块循环引入问题在Node.js如何处理,模块加载规则又有哪些,具体详情请看下文。
模块定义
采用 common.js 的规则, 关键字
exports.a = function () {};
// 或者
module.exports = {
a: function () {}
};
模块的内部变量
__dirname
当前模块的文件夹地址
__filename
当前模块的文件地址
require.main
执行的主入口文件
// node app.js
console.log(module.main); // app模块
require.resolve
获取到模块的绝对路径
// return /path/to/node_modules/express/lib/index.js
require.resolve('express');
exports对象
模块导出对象, 初始对象与module.exports
引用地址一致
module对象
在每个模块中,module 的自由变量是一个指向表示当前模块的对象的引用。
module.children
当前模块引入其它模块的集合, 例子如下
const moduleA = reuqire('moduleA');
require.children = [moduleA];
module.parent对象
最先引入该模块的对象, 例子如下
app.js
// node app.js
const express = require('express');
console.log(module.parent); // undefined
module.exports = express();
main.js
const app = require('./app'); // moduleMain
app.listen(8080, function () {});
module.paths
模块搜索路径, 当前模块require时自动加载的路径
模块安装
全局安装
一般模块
全局安装的模块一般在 npm config get prefix
+ /lib/node_modules目录下, 模块内部依赖安装在自身的 node_modules 中。比如:npm install -g express
/usr/local/lib/node_modules
|-- express
|-- package.json
|-- lib/
|-- node_modules
|-- qs
|-- cookie
|-- ...
携带运行命令的模块
当全局安装 npm install -g gulp
, 模块含有定义的gulp
命令,那么将会链接至npm config get prefix
+ /bin 目录下。
/usr/local/bin
|-- gulp
/usr/local/lib/node_modules
|-- gulp
|-- package.json
|-- bin
|-- gulp.js
|-- node_modules
|-- gulp-util
局部安装
一般模块
局部安装模块与全模块的区别,在于 process.cwd()
+ /node_modules目录下, 同时安装模块和依赖模块一般在同一级目录。
./node_modules
|-- express
|-- package.json
|-- lib/
|-- qs
|-- cookie
|-- ...
携带运行命令的模块
局部安装, bin
目录将移至node_modules/.bin
下
./node_modules
|-- .bin
|-- gulp
|-- gulp
|-- package.json
|-- bin
|-- gulp.js
|-- gulp-util
|-- ...
加载模块机制
相对路径
// 1. 加载 ./a.js
// 2. 加载 ./a.json
// 3. 加载 ./a.node
// 4. 加载 ./a/index.js
// 5. 加载 ./a/index.json
// 6. 加载 ./a/index.node
const a = require('./a');
// 加载上一级目录
const b = require('../b');
绝对路径
const path = require('path');
const c = require('/usr/local/lib/c');
const d = require(path.resolve(__dirname, './d'));
模块名匹配
内置模块优先匹配, 非内置模块利用 module.paths 多个路径, 以及结合 prefix
+ /lib/node_modules加载。
- 尝试加载内置模块 如 fs, http, net, path
- 加载本地模块 ./node_modules/express
- 加载 NODE_PATH目录下的模块 $NODE_PATH/express
- 全局目录加载 $HOME/.node_modules;$HOME/.node_libraries;$PREFIX/lib/node
require.resolve('express');
模块包装器
在执行模块代码之前,Node.js 会使用一个如下的函数包装器将其包装:
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
通过这样做,Node.js 实现了以下几点:
- 它保持了顶层的变量(用 var、const 或 let 定义)作用在模块范围内,而不是全局对象。
它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
- 实现者可以用于从模块中导出值的 module 和 exports 对象
- 包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname
模块循环引入
假设有 a.js
和 b.js
, 在加载 a.js
时 引入了b.js
,同时 b.js
引入了 a.js
, 那么引入顺序时如何的?
实际上
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);
执行结果
$ node main.js
main 开始
a 开始
b 开始
在 b 中,a.done = false
b 结束
在 a 中,b.done = true
a 结束
在 main 中,a.done=true,b.done=true
当 main.js 加载 a.js 时,a.js 又加载 b.js。 此时,b.js 会尝试去加载 a.js。 为了防止无限的循环,会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。