NodeJs中模块的分类
-
内置模块:
也叫原生模块,核心模块,内置模块在安装node.js的时候就已经编译成二 进制文件(保存在lib目录下),可以直接加载运行(速度较快),部分内置模块,在node.exe这个进程启动的时候就已经默认加载了,所以可以直接使用。
例如:fs、path、http
-
文件模块
路径模块:也就是用相对路径或者绝对路径引入的。
即本地包(开发人员自己编写的js文件)
第三方模块(node_modules)
即第三方包,比如从npm上下载,也需要加载编译,较慢。比如
require('mysql')
-
缓存中的模块
require函数加载文件后会将加载结果缓起来,当下次调用
require
函数加载时会直接返回已缓存的内容。
无论是内置模块还是文件模块都可以被缓存
模块查找的优先级
-
从文件模块缓存中加载
尽管原生模块与文件模块的优先级不同,但是都不会优先于从文件模块的缓存 中加载已经存在的模块。
-
从原生模块加载
原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名之后,优先检查模块是否在原生模块列表中。
以http模块为例,尽管在目录下存在一个
http/http.js/http.node/http.json
文件,require(“http”)都不会从这些文件中加载,而是从原生模块中加载。原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。
-
从文件模块加载
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件
require方法接受的参数
http、fs、path
等,原生模块。(内置模块)/mod或../mod
,相对路径的文件模块。(路径模块-开发者自己编写的)/pathtomodule/mod
,绝对路径的文件模块。(路径模块-开发者自己编写的)mod
,非原生模块的文件模块。(第三方模块,从npm下载的)
module.paths
对于每一个被加载的文件模块,创建这个模块对象的时候,这个模块便会有一个paths属性,其值根据当前文件的路径计算得到.
可以看出module.paths
的生成规则为:
- 从当前文件目录开始查找node_modules目录;
- 然后依次进入父目录,查找父目录下的node_modules目录;
- 依次迭代, 直到根目录下的node_modules目录。
除此之外还有一个全局module path
,是当前node执行文件的相对目录 (…/…/lib/node)。
如果在环境变量中设置了HOME目录和NODE_PATH目录的话,整个路径还包含NODE_PATH和HOME目录下的.node_libraries 与.node_modules。
[
NODE_PATH,
HOME/.node_modules,
HOME/.node_libraries,
execPath/../../lib/node
]
require查找策略
-
第一步:如果没有相对路径的话就先判断是否是内核模块
如: require(‘fs’)就是在此时加载进来
-
第二步:如果有相对路径的话,那么就去对应的路径下寻找
如:require(’./util’)或require(’…/util’)
-
第三步:如果没有使用相对路径的话,就认为是第三方包(
require("demo")
),于是开始列举所有可能存在的位置 -
第四步:对于第二步和第三步举出的目录,依次进行搜索,搜索的方式为
加上后缀.js|.json|.node去查找文件,找到就返回
找不到的就把这个路径当成文件夹,在这个文件夹里找package.json里的main属性,找到了就返回这个指定的脚本
还找不到的直接在这个文件夹里查找index.js|index.json|index.node文件,找到了返回,找不到就下一条路径
【整个文件的查找流程如下:】
总结:
在Nodejs中模块加载一般会经历3个步骤,路径分析、文件定位、编译执行
按照模块的分类,按照以下顺序进行优先加载:
系统缓存:模块被执行之后会会进行缓存,首先是先进行缓存加载,判断缓存中是否有值。
系统模块:也就是原生模块,这个优先级仅次于缓存加载,部分核心模块已经被编译成二进制,省略了路径分析、文件定位,直接加载到了内存中,系统模块定义在Node.js源码的lib目录下,可以去查看。
文件模块:优先加载
.、..、/
开头的,如果文件没有加上扩展名,会依次按照.js、.json、.node
进行扩展名补足尝试
目录做为模块:这种情况发生在文件模块加载过程中,也没有找到,但是发现是一个目录的情况,这个时候会将这个目录当作一个包来处理,Node这块采用了Commonjs规范,先会在项目根目录查找package.json文件,取出文件中定义的main属性(
"main": "lib/hello.js"
)描述的入口文件进行加载,也没加载到,则会抛出默认错误:Error: Cannot find module 'lib/hello.js'
node_modules目录加载:对于系统模块、路径文件模块都找不到,Node.js会从当前模块的父目录进行查找,直到系统的根目录
问题小点
模块循环引用
假设有a.js、b.js两个模块相互引用,会有什么问题?是否为陷入死循环?看以下例子
// a.js
console.log('a模块start');
exports.test = 1;
undeclaredVariable = 'a模块未声明变量'
const b = require('./b');
console.log('a模块加载完毕: b.test值:',b.test);
// b.js
console.log('b模块start');
exports.test = 2;
const a = require('./a');
console.log('undeclaredVariable: ', undeclaredVariable);
console.log('b模块加载完毕: a.test值:', a.test);
启动a.js的时候,会加载b.js,那么在b.js中又加载了a.js,但是此时a.js模块还没有执行完,返回的是一个a.js模块的exports对象未完成的副本给到b.js模块。然后b.js完成加载之后将exports对象提供给了a.js模块