Moudule模块
一. 简述
- Node.js 有一个简单的模块加载系统。 在 Node.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)
- 模块内的本地变量是私有的,因为模块被 Node.js 包装在一个函数中,在执行模块代码之前,Node.js 会使用一个如下的函数包装器将其包装:
(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });
- 通过这样做,Node.js 实现了以下几点:
a. 它保持了顶层的变量(用 var、const 或 let 定义)作用在模块范围内,而不是全局对象。
b. 它有助于提供一些看似全局的但实际上是模块特定的变量,例如:实现者可以用于从模块中导出值的 module 和 exports 对象。
c. 包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。
d. 想要获得调用 require() 时加载的确切的文件名,使用 require.resolve(‘文件名’) 函数。
二. 模块的导出
- 导出模块的方式有两种,一种是:
①exports.prop = value;
②module.exports = { }
- exports 变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋予 module.exports 的值。
- 它是一个快捷方式,以便
module.exports.f = ...
可以被更简洁地写成exports.f = ...
。 - 注意:就像任何变量,如果一个新的值被赋值给 exports,它就不再绑定到module.exports
console.log(module.exports===exports)
//true
exports=[1,2,3];
console.log(exports[1])
//2
console.log(module.exports===exports)
//false
三. 将文件作为模块
-
如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、.json 或 .node 拓展名再加载。.js 文件会被解析为 JavaScript 文本文件,.json 文件会被解析为 JSON 文本文件。
-
以
/
为前缀的模块是文件的绝对路径。
例如,require("/S-娜娜酱/3.27_nodejsdemo/js/04module1.js")会加载 “/S-娜娜酱/3.27_nodejsdemo/js/04module1.js” 文件。 -
以
./
为前缀的模块是相对于调用 require() 的文件的。
也就是说,circle.js 必须和 foo.js 在同一目录下以便于 require(’./circle’) 找到它。 -
当没有以
/
./
或../
开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。 -
如果给定的路径不存在,则 require() 会抛出一个 code 属性为 ‘MODULE_NOT_FOUND’ 的 Error。
四. 将目录作为模块
可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。 把目录递给 require() 作为一个参数。
1.在根目录下创建一个package.json
文件,并指定一个 main 模块。
例子,package.json 文件类似:
{
"main": "./lib/a.js"
}
-
如果这是在
./3.28_nodejsdemo
目录中,则require('.')
会试图加载./3.28_nodejsdemo/lib/a.js
。
这就是 Node.js 处理package.json
文件的方式。 -
注意:如果
package.json
中 “main” 入口指定的文件不存在,则无法解析,Node.js 会将模块视为不存在,并抛出默认错误:
Error: Cannot find module 'some-library'
-
如果目录里没有
package.json
文件,则 Node.js 就会试图加载目录下的index.js
或index.node
文件。
五. 从 node_modules 目录加载
//如果文件前没有加/,,
-
如果传递给 require() 的模块标识符不是一个核心模块,也没有以
/
./
../
开头,则 Node.js 会从当前目录下寻找node-modules
文件夹,找不到就一层一层向上级目录里找直到根目录,直到找到为止,尝试从它的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.js 源代码的 lib/ 目录下。
- require() 总是会优先加载核心模块。
例如,require(‘http’) 始终返回内置的 HTTP 模块,即使有同名文件。
7.1 require对象
-
require.cache
:被引入的模块将被缓存在这个对象中。从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块。注意不能删除 native addons(原生插件),因为它们的重载将会导致错误。 -
require.main
:返回主模块路径。
-
require.resolve(request)
:使用内部的 require() 机制查询模块的位置, 此操作只返回解析后的文件名,不会加载该模块。
request 需要解析的模块路径 -
require.resolve.paths(request)
:返回一个数组,其中包含解析 request 过程中被查询的路径。 如果 request 字符串指向核心模块(例如 http 或 fs),则返回 null。
7.2 module 对象
- 在每个模块中,module 的自由变量是一个指向表示当前模块的对象的引用。 为了方便,
module.exports
也可以通过全局模块的 exports对象访问。 module 实际上不是全局的,而是每个模块本地的。 module.children
:被该模块引用的模块对象。module.filename
:模块的完全解析后的文件名。module.id
:模块的标识符。 通常是完全解析后的文件名。module.loaded
:模块是否已经加载完成,或正在加载中。module.parent
:最先引用该模块的模块。module.paths
:模块的搜索路径。