- Node简介
- 第一个node程序
- module(模块系统)
- npm包管理器
- 模块系统优先级
- 认识http内置模块
- url内置模块
- path内置模块
- fs内置模块
- http模块服务端进阶
- http报文浅析
- url模块进阶
- path模块进阶
- querystring模块进阶
- 了解Buffer和Stream
- os模块
- Buffer模块
- Stream模块
- http模块客户端
- Cookie浅析
Node.js 有一个简单的模块加载系统。 在 Node.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)。
例子,假设有一个名为 foo.js
的文件:
const circle = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`);
在第一行中,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
对象上指定额外的属性,函数和对象可以被添加到模块的根部。
模块的原理
在这个例子中,变量 PI
是 circle.js
私有的。模块内的本地变量是私有的,因为模块被 Node.js 包装在一个函数中,在执行模块代码之前,Node.js 会使用一个如下的函数包装器将其包装:
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
通过这样做,Node.js 实现了以下几点:
- 它保持了顶层的变量(用
var
、const
或let
定义)作用在模块范围内,而不是全局对象。 - 它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
- 实现者可以用于从模块中导出值的
module
和exports
对象。 - 包含模块绝对文件名和目录路径的快捷变量
__filename
和__dirname
。
- 实现者可以用于从模块中导出值的
想要获得调用 require()
时加载的确切的文件名,使用 require.resolve()
函数。
我们使用 require.main === module
来判断一个文件是直接运行,还是被模块机制引入的。
模块别名
导出模块的方式有两种,一种是:
exports.prop = value;
或者:
module.exports = {
}
exports
变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋予 module.exports
的值。
它是一个快捷方式,以便 module.exports.f = ...
可以被更简洁地写成 exports.f = ...
。 注意,就像任何变量,如果一个新的值被赋值给 exports
,它就不再绑定到module.exports
将文件作为模块
如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js
、.json
或 .node
拓展名再加载。
.js
文件会被解析为 JavaScript 文本文件,.json
文件会被解析为 JSON 文本文件。
以 '/'
为前缀的模块是文件的绝对路径。 例如,require('/home/marco/foo.js')
会加载 /home/marco/foo.js
文件。
以 './'
为前缀的模块是相对于调用 require()
的文件的。 也就是说,circle.js
必须和 foo.js
在同一目录下以便于 require('./circle')
找到它。
当没有以 '/'
、'./'
或 '../'
开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules
目录。
如果给定的路径不存在,则 require()
会抛出一个 code
属性为 'MODULE_NOT_FOUND'
的 Error
。
将目录作为模块
可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。 把目录递给 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.js
或 index.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
的位置。 后缀路径同样遵循模块的解析语法。
缓存
模块在第一次加载后会被缓存。 这也意味着(类似其他缓存机制)如果每次调用 require('foo')
都解析到同一文件,则返回相同的对象。
多次调用 require(foo)
不会导致模块的代码被执行多次。 这是一个重要的特性。 借助它, 可以返回“部分完成”的对象,从而允许加载依赖的依赖, 即使它们会导致循环依赖。
如果想要多次执行一个模块,可以导出一个函数,然后调用该函数。
模块缓存的注意事项
模块是基于其解析的文件名进行缓存的。 由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如从 node_modules
目录加载),这样就不能保证 require('foo')
总能返回完全相同的对象。
此外,在不区分大小写的文件系统或操作系统中,被解析成不同的文件名可以指向同一文件,但缓存仍然会将它们视为不同的模块,并多次重新加载。 例如,require('./foo')
和 require('./FOO')
返回两个不同的对象,而不会管 ./foo
和 ./FOO
是否是相同的文件。
核心模块
Node.js 有些模块会被编译成二进制。 这些模块别的地方有更详细的描述。
核心模块定义在 Node.js 源代码的 lib/
目录下。
require()
总是会优先加载核心模块。 例如,require('http')
始终返回内置的 HTTP 模块,即使有同名文件。
require对象
-
require.cache:被引入的模块将被缓存在这个对象中。从此对象中删除键值对将会导致下一次
require
重新加载被删除的模块。注意不能删除 native addons(原生插件),因为它们的重载将会导致错误。 -
require.main:返回主模块路径。
-
require.resolve(request):使用内部的
require()
机制查询模块的位置, 此操作只返回解析后的文件名,不会加载该模块。request
需要解析的模块路径。
-
require.resolve.paths(request):返回一个数组,其中包含解析
request
过程中被查询的路径。 如果request
字符串指向核心模块(例如http
或fs
),则返回null
。
module 对象
在每个模块中,module
的自由变量是一个指向表示当前模块的对象的引用。 为了方便,module.exports
也可以通过全局模块的 exports
对象访问。 module
实际上不是全局的,而是每个模块本地的。
- module.children:被该模块引用的模块对象。
- module.filename:模块的完全解析后的文件名。
- module.id:模块的标识符。 通常是完全解析后的文件名。
- module.loaded:模块是否已经加载完成,或正在加载中。
- module.parent:最先引用该模块的模块。
- module.paths:模块的搜索路径。