Node.js v11.14.0 文档


module(模块)#

查看v10.x中文文档

Stability: 2 - Stable

In the Node.js module system, each file is treated as a separate module. For example, consider a file named foo.js:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

On the first line, foo.js loads the module circle.js that is in the same directory as foo.js.

Here are the contents of circle.js:

const { PI } = Math;

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

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

The module circle.js has exported the functions area() and circumference(). Functions and objects are added to the root of a module by specifying additional properties on the special exports object.

Variables local to the module will be private, because the module is wrapped in a function by Node.js (see module wrapper). In this example, the variable PI is private to circle.js.

The module.exports property can be assigned a new value (such as a function or object).

Below, bar.js makes use of the square module, which exports a Square class:

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`The area of mySquare is ${mySquare.area()}`);

The square module is defined in square.js:

// Assigning to exports will not modify module, must use module.exports
module.exports = class Square {
  constructor(width) {
    this.width = width;
  }

  area() {
    return this.width ** 2;
  }
};

The module system is implemented in the require('module') module.

访问主模块#

中英对照提交修改

当 Node.js 直接运行一个文件时, require.main 会被设为它的 module。 这意味着可以通过 require.main === module 来判断一个文件是否被直接运行:

对于 foo.js 文件,如果通过 node foo.js 运行则为 true,但如果通过 require('./foo') 运行则为 false

因为 module 提供了一个 filename 属性(通常等同于 __filename),所以可以通过检查 require.main.filename 来获取当前应用程序的入口点。

附录:包管理器的技巧#

中英对照提交修改

Node.js 的 require() 函数的语义被设计得足够通用化,可以支持许多合理的目录结构。 包管理器程序(如 dpkgrpmnpm)可以不用修改就能够从 Node.js 模块构建本地包。

以下是一个推荐的目录结构:

假设想要在 /usr/lib/node/<some-package>/<some-version> 目录中保存一个特定版本的包的内容。

包可以依赖于其他包。 为了安装包 foo,可能需要安装一个指定版本的 bar 包。 bar 包也可能有依赖,且在某些情况下,依赖可能有冲突或形成循环。

因为 Node.js 会查找它所加载的模块的实际路径(也就是说会解析符号链接),然后在 node_modules 目录中寻找它们的依赖,如下所述,这种情况使用以下体系结构很容易解决:

  • /usr/lib/node/foo/1.2.3/ - foo 包的内容,版本 1.2.3。
  • /usr/lib/node/bar/4.3.2/ - foo 依赖的 bar 包的内容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar - /usr/lib/node/bar/4.3.2/ 的符号链接。
  • /usr/lib/node/bar/4.3.2/node_modules/* - bar 所依赖的包的符号链接

因此,即便存在循环依赖或依赖冲突,每个模块还是可以获得它所依赖的包的一个可用版本。

foo 包中的代码调用 require('bar'),它会获得符号链接 /usr/lib/node/foo/1.2.3/node_modules/bar 指向的版本。 然后,当 bar 包中的代码调用 require('queue'),它会获得符号链接 /usr/lib/node/bar/4.3.2/node_modules/quux 指向的版本。

此外,为了进一步优化模块查找过程,不要将包直接放在 /usr/lib/node 目录中,而是将它们放在 /usr/lib/node_modules/<name>/<version> 目录中。 这样 Node.js 就不会在 /usr/node_modules/node_modules 目录中查找缺失的依赖。

为了使模块在 Node.js 的 REPL 中可用,可能需要将 /usr/lib/node_modules 目录添加到 $NODE_PATH 环境变量中。 由于在 node_modules 目录中查找模块使用的是相对路径,而调用 require() 的文件是基于实际路径的,因此包本身可以放在任何地方。

总结#

中英对照提交修改

想要获得调用 require() 时加载的确切的文件名,使用 require.resolve() 函数。

综上所述,以下用伪代码描述的高级算法,解释 require.resolve() 做了些什么:

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with '/'
   a. set Y to be the filesystem root
3. If X begins with './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
4. LOAD_NODE_MODULES(X, dirname(Y))
5. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text.  STOP
2. If X.js is a file, load X.js as JavaScript text.  STOP
3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
4. If X.node is a file, load X.node as binary addon.  STOP

LOAD_INDEX(X)
1. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon.  STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. let M = X + (json main field)
   c. LOAD_AS_FILE(M)
   d. LOAD_INDEX(M)
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_AS_FILE(DIR/X)
   b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
   a. if PARTS[I] = "node_modules" CONTINUE
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIRS + DIR
   d. let I = I - 1
5. return DIRS

缓存#

查看v10.x中文文档

Modules are cached after the first time they are loaded. This means (among other things) that every call to require('foo') will get exactly the same object returned, if it would resolve to the same file.

Provided require.cache is not modified, multiple calls to require('foo') will not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.

To have a module execute code multiple times, export a function, and call that function.

模块缓存的注意事项#

查看v10.x中文文档

Modules are cached based on their resolved filename. Since modules may resolve to a different filename based on the location of the calling module (loading from node_modules folders), it is not a guarantee that require('foo') will always return the exact same object, if it would resolve to different files.

Additionally, on case-insensitive file systems or operating systems, different resolved filenames can point to the same file, but the cache will still treat them as different modules and will reload the file multiple times. For example, require('./foo') and require('./FOO') return two different objects, irrespective of whether or not ./foo and ./FOO are the same file.

核心模块#

中英对照提交修改

Node.js 有些模块会被编译成二进制。 这些模块别的地方有更详细的描述。

核心模块定义在 Node.js 源代码的 lib/ 目录下。

require() 总是会优先加载核心模块。 例如, require('http') 始终返回内置的 HTTP 模块,即使有同名文件。

循环#

中英对照提交修改

当循环调用 require() 时,一个模块可能在未完成执行时被返回。

例如以下情况:

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.jsexports 对象的 未完成的副本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

需要仔细的规划, 以允许循环模块依赖在应用程序内正常工作.

文件模块#

查看v10.x中文文档

If the exact filename is not found, then Node.js will attempt to load the required filename with the added extensions: .js, .json, and finally .node.

.js files are interpreted as JavaScript text files, and .json files are parsed as JSON text files. .node files are interpreted as compiled addon modules loaded with process.dlopen().

A required module prefixed with '/' is an absolute path to the file. For example, require('/home/marco/foo.js') will load the file at /home/marco/foo.js.

A required module prefixed with './' is relative to the file calling require(). That is, circle.js must be in the same directory as foo.js for require('./circle') to find it.

Without a leading '/', './', or '../' to indicate a file, the module must either be a core module or is loaded from a node_modules folder.

If the given path does not exist, require() will throw an Error with its code property set to 'MODULE_NOT_FOUND'.

目录作为模块#

中英对照提交修改

可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。 把目录递给 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"main" 入口指定的文件不存在,则无法解析,Node.js 会将模块视为不存在,并抛出默认错误:

Error: Cannot find module 'some-library'

如果目录里没有 package.json 文件,则 Node.js 就会试图加载目录下的 index.jsindex.node 文件。 例如,如果上面的例子中没有 package.json 文件,则 require('./some-library') 会试图加载:

  • ./some-library/index.js
  • ./some-library/index.node

从 node_modules 目录加载#

中英对照提交修改

如果传递给 require() 的模块标识符不是一个核心模块,也没有以 '/''../''./' 开头,则 Node.js 会从当前模块的父目录开始,尝试从它的 /node_modules 目录里加载模块。 Node.js 不会附加 node_modules 到一个已经以 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 的位置。 后缀路径同样遵循模块的解析语法。

从全局目录加载#

查看v10.x中文文档

If the NODE_PATH environment variable is set to a colon-delimited list of absolute paths, then Node.js will search those paths for modules if they are not found elsewhere.

On Windows, NODE_PATH is delimited by semicolons (;) instead of colons.

NODE_PATH was originally created to support loading modules from varying paths before the current module resolution algorithm was defined.

NODE_PATH is still supported, but is less necessary now that the Node.js ecosystem has settled on a convention for locating dependent modules. Sometimes deployments that rely on NODE_PATH show surprising behavior when people are unaware that NODE_PATH must be set. Sometimes a module's dependencies change, causing a different version (or even a different module) to be loaded as the NODE_PATH is searched.

Additionally, Node.js will search in the following list of GLOBAL_FOLDERS:

  • 1: $HOME/.node_modules
  • 2: $HOME/.node_libraries
  • 3: $PREFIX/lib/node

Where $HOME is the user's home directory, and $PREFIX is Node.js's configured node_prefix.

These are mostly for historic reasons.

It is strongly encouraged to place dependencies in the local node_modules folder. These will be loaded faster, and more reliably.

模块封装器#

中英对照提交修改

在执行模块代码之前,Node.js 会使用一个如下的函数封装器将其封装:

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});

通过这样做,Node.js 实现了以下几点:

  • 它保持了顶层的变量(用 varconstlet 定义)作用在模块范围内,而不是全局对象。
  • 它有助于提供一些看似全局的但实际上是模块特定的变量,例如:  - 实现者可以用于从模块中导出值的 moduleexports 对象。  - 包含模块绝对文件名和目录路径的快捷变量 __filename__dirname

模块作用域#

__dirname#

中英对照提交修改

当前模块的目录名。 与 __filenamepath.dirname() 相同。

示例,从 /Users/mjr 运行 node example.js

console.log(__dirname);
// 打印: /Users/mjr
console.log(path.dirname(__filename));
// 打印: /Users/mjr

__filename#

中英对照提交修改

当前模块的文件名。 这是当前的模块文件的绝对路径(符号链接会被解析)。

对于主程序,这不一定与命令行中使用的文件名相同。

有关当前模块的目录名,参阅 __dirname

示例:

从 /Users/mjr 运行 node example.js

console.log(__filename);
// 打印: /Users/mjr/example.js
console.log(__dirname);
// 打印: /Users/mjr

给定两个模块:a 和 b,其中 ba 的依赖文件,且目录结构如下:

  • /Users/mjr/app/a.js
  • /Users/mjr/app/node_modules/b/b.js

b.js 中的 __filename 的引用会返回 /Users/mjr/app/node_modules/b/b.js,而 a.js 中的 __filename 的引用会返回 /Users/mjr/app/a.js

exports#

查看v10.x中文文档

A reference to the module.exports that is shorter to type. See the section about the exports shortcut for details on when to use exports and when to use module.exports.

module#

查看v10.x中文文档

A reference to the current module, see the section about the module object. In particular, module.exports is used for defining what a module exports and makes available through require().

require(id)#

暂无中英对照

  • id <string> module name or path
  • Returns: <any> exported module content

Used to import modules, JSON, and local files. Modules can be imported from node_modules. Local modules and JSON files can be imported using a relative path (e.g. ./, ./foo, ./bar/baz, ../foo) that will be resolved against the directory named by __dirname (if defined) or the current working directory.

// Importing a local module:
const myLocalModule = require('./path/myLocalModule');

// Importing a JSON file:
const jsonData = require('./path/filename.json');

// Importing a module from node_modules or Node.js built-in module:
const crypto = require('crypto');

require.cache#

查看v10.x中文文档

Modules are cached in this object when they are required. By deleting a key value from this object, the next require will reload the module. Note that this does not apply to native addons, for which reloading will result in an error.

Adding or replacing entries is also possible. This cache is checked before native modules and if a name matching a native module is added to the cache, no require call is going to receive the native module anymore. Use with care!

require.extensions#

暂无中英对照

稳定性: 0 - 废弃

Instruct require on how to handle certain file extensions.

Process files with the extension .sjs as .js:

require.extensions['.sjs'] = require.extensions['.js'];

Deprecated. In the past, this list has been used to load non-JavaScript modules into Node.js by compiling them on-demand. However, in practice, there are much better ways to do this, such as loading modules via some other Node.js program, or compiling them to JavaScript ahead of time.

Avoid using require.extensions. Use could cause subtle bugs and resolving the extensions gets slower with each registered extension.

require.main#

暂无中英对照提交修改

The Module object representing the entry script loaded when the Node.js process launched. See "Accessing the main module".

In entry.js script:

console.log(require.main);
node entry.js
Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/absolute/path/to/entry.js',
  loaded: false,
  children: [],
  paths:
   [ '/absolute/path/to/node_modules',
     '/absolute/path/node_modules',
     '/absolute/node_modules',
     '/node_modules' ] }

require.resolve(request[, options])#

中英对照提交修改

  • request <string> 需要解析的模块路径。
  • options <Object>

    • paths <string[]> 解析模块的起点路径。此参数存在时,将使用这些路径而非默认解析路径。    注意此数组中的每一个路径都被用作模块解析算法的起点,意味着 node_modules 层级将从这里开始查询。
  • Returns: <string>

使用内部的 require() 机制查询模块的位置, 此操作只返回解析后的文件名,不会加载该模块。

require.resolve.paths(request)#

中英对照提交修改

返回一个数组,其中包含解析 request 过程中被查询的路径。 如果 request 字符串指向核心模块(例如 httpfs),则返回 null

module 对象#

中英对照提交修改

在每个模块中, module 的自由变量是对表示当前模块的对象的引用。 为方便起见,还可以通过全局模块的 exports 访问 module.exportsmodule 实际上不是全局的,而是每个模块本地的。

module.children#

中英对照提交修改

被该模块引用的模块对象。

module.exports#

中英对照提交修改

module.exports 对象由 Module 系统创建。 有时这是不可接受的;许多人希望他们的模块成为某个类的实例。 为此,需要将期望导出的对象赋值给 module.exports。 注意,将期望的对象赋值给 exports 会简单地重新绑定本地的 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 已准备好');
});

注意,对 module.exports 的赋值必须立即完成。 不能在任何回调中完成。 以下是不起作用的:

x.js:

setTimeout(() => {
  module.exports = { a: 'hello' };
}, 0);

y.js:

const x = require('./x');
console.log(x.a);

exports 快捷方式#

中英对照提交修改

exports 变量是在模块的文件级作用域内可用的,且在模块执行之前赋值给 module.exports

它允许使用快捷方式,因此 module.exports.f = ... 可以更简洁地写成 exports.f = ...。 但是,就像任何变量一样,如果为 exports 赋予了新值,则它将不再绑定到 module.exports

module.exports.hello = true; // 从模块的引用中导出。
exports = { hello: false };  // 不导出,仅在模块中可用。

module.exports 属性被新对象完全替换时,通常也会重新赋值 exports

module.exports = exports = function Constructor() {
  // ... 
};

为了说明这种行为,想象对 require() 的假设实现,它与 require() 的实际实现非常类似:

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // 模块代码在这。在这个例子中,定义了一个函数。
    function someFunc() {}
    exports = someFunc;
    // 此时,exports 不再是一个 module.exports 的快捷方式,
    // 且这个模块依然导出一个空的默认对象。
    module.exports = someFunc;
    // 此时,该模块导出 someFunc,而不是默认对象。
  })(module, module.exports);
  return module.exports;
}

module.filename#

查看v10.x中文文档

The fully resolved filename of the module.

module.id#

中英对照提交修改

模块的标识符。 通常是完全解析后的文件名。

module.loaded#

中英对照提交修改

模块是否已经加载完成,或正在加载中。

module.parent#

中英对照提交修改

最先引用该模块的模块。

module.paths#

中英对照提交修改

模块的搜索路径。

module.require(id)#

查看v10.x中文文档

The module.require() method provides a way to load a module as if require() was called from the original module.

In order to do this, it is necessary to get a reference to the module object. Since require() returns the module.exports, and the module is typically only available within a specific module's code, it must be explicitly exported in order to be used.

Module 对象#

中英对照提交修改

Module 实例提供通用方法。 module 变量常见于文件模块中。 通过 require('module') 获取。

module.builtinModules#

中英对照提交修改

罗列 Node.js 提供的所有模块名称。可以用来判断模块是否为第三方所维护。

注意, module 在此处含义与模块封装器所提供的 module 是不同的。可以通过引入 Module 模块访问:

const builtin = require('module').builtinModules;

module.createRequireFromPath(filename)#

查看v10.x中文文档

  • filename <string> Filename to be used to construct the relative require function.
  • Returns: <require> Require function
const { createRequireFromPath } = require('module');
const requireUtil = createRequireFromPath('../src/utils');

// Require `../src/utils/some-tool`
requireUtil('./some-tool');