3

介绍

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加载。

  1. 尝试加载内置模块 如 fs, http, net, path
  2. 加载本地模块 ./node_modules/express
  3. 加载 NODE_PATH目录下的模块 $NODE_PATH/express
  4. 全局目录加载 $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.jsb.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 模块。

参考资料


wayneli
1.4k 声望828 粉丝

2017-2018年目标