nodejs模块查找策略

我们都知道,nodejs目前支持es6模块和commonjs模块,在这个万物皆可为模块的世界里,这篇文章来探讨一下当我们引用一个模块时,nodejs是已一种什么样的策略来查找到对应的模块的

模块分类

nodejs中的模块主要分为两大类,一类是nodejs提供的模块,称为核心模块,另一类是用户编写的模块,称为文件模块,他们的关系如下图所示

文件模块

文件模块根据引用的方式不同,又分为自定义模块和以路径形式引入的模块,自定义模块通常是以包的形式存在,例如我们从npm上下载下来的包,就是自定义模块。以路径形式引入的模块,通常是用户自己编写封装的模块,引入时,用户需要制定模块的真实路径,二者的区别是引入方式不同,例如以下的代码

var express = require('express');//自定义模块
var usersRouter = require('./routes/users');//以路径形式引入的模块

二者的相同点在于由于同属文件模块,引入时都是运行时动态加载的,同时由于动态加载,引入时需要进行路径分析,文件定位,动态编译等操作

核心模块

核心模块可以理解为nodejs的内置核心功能模块,它在nodejs源代码编译的时候就会被编译成二进制文件,nodejs启动时,这些核心模块就会直接被加载进内存,所以核心模块加载时,相对于文件模块,核心模块引用时不需要进行文件定位和动态编译,速度上有一定的优势,如http,fs,path等常用模块都属于核心模块

加载顺序

先下结论:缓存>核心模块>文件模块

nodejs对于模块加载做了一定程度的优化,所有的文件只要引用过一次,就会被缓存起来,下一次引用时,会先检查缓存中有没有对应的文件,优先从缓存中进行加载,已减少二次开销。但不同于浏览器的缓存,nodejs缓存的是经过分析和编译后的对象,不管是文件模块还是核心模块,二者引用前都会先检查缓存,但核心模块的缓存检查要比文件模块的缓存检查优先。

路径分析

  • 核心模块:

由于核心模块是在nodejs编译时就被加载进内存,所以核心模块不需要进行路径分析和文件定位,其加载速度仅此于缓存

  • 路径形式引入的模块:

 由于指定了确切的路径,引入时require方法会把指定的路径转化为硬盘上的真实路径,并用这个路径作为索引将编译后的结果进行缓存。由于指定了路径,这种形式的文件模块在路径分析时可以节省大量的时间,其速度比自定义模块要快,但是比核心模块要慢

  • 自定义模块:

自定义模块的加载相对于以路径形式引入的模块要复杂一些,主要在于路径分析和文件定位时会耗费比较多的时间,自定义组件的查找遵循以下的规则:

  1. 查找当前目录下的node_modules目录,看是否有匹配项,如有,命中文件
  2. 寻找父目录的下的node_modules,如有,命中文件
  3. 按照这个规则一直往父目录搜索直到到根目录下的node_modules

由于必须一层一层的的查找,自定义模块的路径分析需要耗费大量的事件,会导致搜索效率较为低下,所以自定义模块的加载性能要比以路径形式加载的方式要慢

文件定位

当完成路径分析之后,node会对文件进行扩展名进行分析,目前commonjs规范和es6的模块都允许文件的标识符不带扩展名,如果标识符不带上扩展名,则会按照.js,.node,.json这个顺序逐个进行尝试

当然,还存在一种情况,就是当前路径命中的不是一个文件,而是一个目录,那么:

  1. 首先会在命中的目录下寻找package.json这个文件并用JSON.parse进行解析,取出json文件中main属性的值,作为命中的文件
  2. 如果找不到package.json或者对应的main属性,那么会用这个目录下面index文件作为命中文件,依旧是按照.js,.node,.json这个顺序逐个进行尝试
  3. 如果依旧找不到index,那么此次文件定位失败,将会按照上面提到的路径遍历规则,往上一级继续寻找

举个栗子

对于路径分析和文件定位我们可以看下面的例子加深一下理解

下图是整个项目的目录结构

上面包含三个模块,分别是nameByJson,parent,root,对应的代码分别如下

nameByJson

//目录:moduleLocation/child/child2/node_modules/nameByJson/real.js
module.exports = function nameByJson() {
    console.log('nameByJson');
};
//目录:moduleLocation/child/child2/node_modules/nameByJson/package.json
{
  "main": "real.js"
}

parent

//目录:moduleLocation/child/node_modules/parent/index.js
module.exports = function parent() {
    console.log('parent');
};

root

//目录:moduleLocation/node_modules/root.js
module.exports = function root() {
    console.log('root');
}

入口文件

//目录:moduleLocation/child/child2/index.js
var nameByJson = require('nameByJson');
var parent=require('parent');
var root =require('root');
nameByJson();//输出nameByJson
parent();//输出parent
root();//输出root

我们运行入口文件index.js,可以发现输出和我们的预期一致,这里是示例代码的源文件,有兴趣的可以下载下来瞄一下

猜你喜欢

转载自blog.csdn.net/xiaomingelv/article/details/96461786