一、模块
http://nodejs.cn/api/modules.html
1. 模块概念
在 Node.js 模块系统中,每个文件都被视为一个独立的模块。 例如,假设有一个名为 foo.js
的文件:
const circle = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`);
在第一行中, foo.js
加载了与 foo.js
在同一目录中的 circle.js
模块。
以下是 circle.js
的内容:
const { PI } = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r;
circle.js
模块导出了 area()
和 circumference()
函数。通过在特殊的 exports
对象上指定额外的属性,可以将函数和对象添加到模块的根部。
2. 模块加载
- Node.js 的
require()
函数的语义被设计得足够通用化,可以支持许多合理的目录结构。require()
总是会优先加载核心模块。 例如,require('http')
始终返回内置的 HTTP 模块,即使有同名文件。
2.1. 文件模块加载
如果按确切的文件名没有找到模块,则 Node.js 会尝试带上
.js
、.json
等拓展名再加载。以
'/'
为前缀的模块是文件的绝对路径。 例如,require('/home/marco/foo.js')
会加载/home/marco/foo.js
文件。以
'./'
为前缀的模块是相对于调用require()
函数所在文件的相对路径。当没有以
'/'
、'./'
或'../'
开头来表示文件时,这个模块必须是一个核心模块或加载自node_modules
目录。
2.2. 目录模块加载
可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。
把目录递给 require()
作为一个参数,有三种方式。
第一种方式是在根目录下创建一个 package.json
文件,并指定一个 main
模块。 例子, package.json
文件类似:
{
"name" : "some-library",
"main" : "./lib/some-library.js"
}
如果这是在 ./some-library
目录中,则 require('./some-library')
会试图加载 ./some-library/lib/some-library.js
。
这就是 Node.js 处理 package.json
文件的方式。
如果目录里没有 package.json
文件,则 Node.js 就会试图加载目录下的 index.js
或 index.node
文件。 例如,如果上面的例子中没有 package.json
文件,则 require('./some-library')
会试图加载:
./some-library/index.js
./some-library/index.node
2.3. 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
3. 模块作用域
3.1. __dirname
当前模块的目录名。 与 __filename
的 path.dirname()
相同。
示例,从 /Users/mjr
运行 node example.js
:
console.log(__dirname);
// 打印: /Users/mjr
console.log(path.dirname(__filename));
// 打印: /Users/mjr
3.2. __filename
当前模块的文件名。 这是当前的模块文件的绝对路径。
示例:从 /Users/mjr
运行 node example.js
:
console.log(__filename);
// 打印: /Users/mjr/example.js
console.log(__dirname);
// 打印: /Users/mjr
3.3. exports
这是一个对于 module.exports
的更简短的引用形式。
3.4. module
对当前模块的引用。 module.exports
用于指定一个模块所导出的内容,即可以通过 require()
访问的内容。
3.5. require()
用于引入模块、 JSON
、或本地文件。 可以从 node_modules
引入模块。 可以使用相对路径(例如 ./
、 ./foo
、 ./bar/baz
、 ../foo
)引入本地模块或 JSON 文件,路径会根据 __dirname
定义的目录名或当前工作目录进行处理。
// 引入本地模块:
const myLocalModule = require('./path/myLocalModule');
// 引入 JSON 文件:
const jsonData = require('./path/filename.json');
// 引入 node_modules 模块或 Node.js 内置模块:
const crypto = require('crypto');
3.6. require.resolve(request)
- request
:
<string> 待解析的模块路径(相对路径) - returns: <string> 解析后的模块路径(绝对路径)
使用内部的 require()
机制查询模块的位置(绝对路径), 此操作只返回解析后的文件名,不会加载该模块。
示例:在 Node.js 中使用 fs 读取文件的时候,经常碰到要拼一个文件的绝对路径的问题 (fs 处理相对路径均以进程执行目录为准)。
之前一直的方法都是,使用 path 模块以及 __dirname 变量 。
fs.readFileSync(path.join(__dirname, './assets/file.txt'));
使用 require.resolve 可以简化这一过程
fs.readFileSync(require.resolve('./assets/file.txt'));
此外, require.resolve 还会在拼接好路径之后检查该路径是否存在, 如果 resolve 的目标路径不存在, 就会抛出 Cannot find module './file.txt'
的异常, 省略了一道检查文件是否存在的工序 (fs.exists).
4. module 对象
4.1. module.exports
module.exports
对象由 Module
系统创建。导出对象需要将对象赋值给 module.exports
。
例如,假设正在创建一个名为 a.js
的模块:
const EventEmitter = require('events');
module.exports = new EventEmitter();
// 处理一些工作,并在一段时间后从模块自身触发 'ready' 事件。
setTimeout(() => {
module.exports.emit('ready');
}, 1000);
然后,在另一个文件中可以这么做:
const a = require('./a');
a.on('ready', () => {
console.log('模块 a 已准备好');
});
4.2. exports
这是一个对于 module.exports
的更简短的引用形式。module.exports.f = ...
可以更简洁地写成 exports.f = ...
二、path(路径)
path
模块提供用于处理文件路径和目录路径的实用工具。 它可以使用以下方式访问:
const path = require('path');
2.1. path.dirname(path)
path.dirname()
方法返回 path
的目录名。
path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'
2.2. path.join([...paths])
path.join()
方法使用平台特定的分隔符将所有给定的 path
片段连接在一起。
零长度的 path
片段会被忽略。 如果连接的路径字符串是零长度的字符串,则返回 '.'
,表示当前工作目录。
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: '/foo/bar/baz/asdf'
path.join('foo', {}, 'bar');
// 抛出 'TypeError: Path must be a string. Received {}'
2.3. path.resolve([...paths])
path.resolve()
方法将路径或路径片段的序列解析为绝对路径。
每个参数都类似在当前目录执行一个cd
操作,从左到右执行,返回的是最后的当前目录(pwd)!
- 如果在处理完所有给定的
path
片段之后还未生成绝对路径,则再加上当前工作目录。 - 生成的路径需要删除尾部斜杠。
- 零长度的
path
片段会被忽略。 - 如果没有传入
path
片段,则path.resolve()
将返回当前工作目录的绝对路径。
path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// 如果当前工作目录是 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
参考:小tips:path的join和resolve的使用区别
三、process(进程)
process
对象是一个全局变量,它提供有关当前 Node.js 进程的信息并对其进行控制。 作为一个全局变量,它始终可供 Node.js 应用程序使用,无需使用 require()
。
3.1. process.env
process.env
属性返回包含用户环境的对象。
process.env.foo = 'bar';
console.log(process.env.foo);
// => bar