Node.js 模块

Node.js 中文文档 modules
《Node.js 实战》
流程图工具 processon

Node 功能的组织及重用

用目录和单独的文件组织起来的代码找起来要比整个程序代码都放在一个长文件中找起来更容易。

重用的问题

在某些语言中,例如
PHPRuby,整合另一个文件(我们称之为“include”文件)中的逻辑,可能意味着在被引入文件中执行的逻辑会影响全局作用域。

也就是说被引入文件常见的任何变量,以及声明的任何函数都可能会覆盖包含它的应用程序所创建的变量和声明的函数。

PHP 中可以使用命名空间避免这个问题,Ruby 通过模块也提供了类似的功能。

Node的重用

Node 的做法不会让开发者有机会在不经意间污染全局命名空间。

Node.js 有一个简单的模块加载系统。 在 Node.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)。

Node 模块允许你从被引入文件中选择要暴露给程序的函数和变量。如果模块返回的函数或变量不值一个,那么它通过设定 exports 对象的属性来指明它们。如果模块只返回一个函数或变量,则可以设定 module.exports 属性。

exports

假设有一个名为 foo.js 的文件:

1
2
3
//foo.js
const circle = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`);

在第一行中,foo.js 加载了同一目录下的 circle.js 模块。

circle.js 文件的内容为:

1
2
3
4
5
6
//circle.js
const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r;

circle.js 模块导出了 area()circumference() 两个函数。 通过在特殊的 exports 对象上指定额外的属性,函数和对象可以被添加到模块的根部。

模块内的本地变量是私有的,因为模块被 Node.js 包装在一个函数中。

module.exports

如下,bar.js 会用到 square 模块,square 导出一个构造函数:

1
2
3
const square = require('./square.js');
const mySquare = square(2);
console.log(`正方形的面积是 ${mySquare.area()}`);

square 模块定义在 square.js 中:

1
2
3
4
5
6
// 赋值给 `exports` 不会修改模块,必须使用 `module.exports`
module.exports = (width) => {
return {
area: () => width ** 2
};
};

使用场景

如果只需要从模块中得到一个函数,那么从 require 中返回一个函数的代码要比返回一个对象的代码更优雅。

实例,以下是一个货币转换函数:

1
2
3
4
5
6
//test-currency.js
var Currency = require('./currency');
var canadianDollar = 0.91;

var currency = new Currency(canadianDollar);
console.log(currency.canadianToUS(50));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//currency.js
var Currency = function(canadianDollar) {
this.canadianDollar = canadianDollar;
};

//随机一个两位小数
Currency.prototype.roundTwoDecimals = function(amount) {
return Math.round(amount * 100) / 100;
};

//加元转美元
Currency.prototype.canadianToUS = function(candian) {
return this.roundTwoDecimals(candian * this.canadianDollar);
};

//美元转加元
Currency.prototype.USToCanadian = function(us) {
return this.roundTwoDecimals(us / this.canadianDollar);
};

module.exports = Currency;

另一个实例,搜索二叉树。

用 node_modules重用模块

Node 中有一个独特的模块引入机制,可以不必知道模块在文件系统中具体位置,这个机制就是 node_modules 目录。

例如前面的 var Currency = require('./currency');,不写 ./, node 会遵照几个规则来寻找这个模块。

用环境变量 NODE_PATH 可以改变 Node 模块的默认路径。

注意事项

  • 如果模块式目录,在木块目录中定义模块文件必须被命名为 index.js ,除非你在这个目录下 packjson.json 文件里特别指明。

  • Node 能把模块作为对象缓存起来。如果程序中两个文件引入了相同模块,第一个文件会把模块返回的数据存到程序的内存中,这样第二个文件就不用再去访问和计算模块的源文件了。

  • requireNode 中少数几个同步 I/O 操作,在 I/O 密集的地方尽量不要用 require, 所有同步调动都会阻塞 Node,直到调用完成才能做其他事情。所以通常只在程序最初加载时才使用 require 和其他同步操作。