require的加载顺序

模块

一个nodejs文件就是一个模块。nodejs的模块分为两类:

(1)原生(核心)模块

(2)文件模块

文件模块分为三类:

  • .js。通过fs模块同步读取js文件并编译执行。
  • .node。通过C/C++进行编写的Addon。通过dlopen方法进行加载。
  • .json。读取文件,调用JSON.parse解析加载。

export公开模块的接口,require从外部获取一个接口。

require查找策略

原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

当require一个文件模块时,从当前文件目录开始查找node_modules目录;然后依次进入父目录,查找父目录下的node_modules目录;依次迭代,直到根目录下的node_modules目录。

简而言之,如果require绝对路径的文件,查找时不会去遍历每一个node_modules目录,其速度最快。其余流程如下:

  1. 从module path数组中取出第一个目录作为查找基准。
  2. 直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找。
  3. 尝试添加.js、.json、.node后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。
  4. 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
  5. 尝试查找该文件,如果存在,则结束查找。如果不存在,则进行第3条查找。
  6. 如果继续失败,则取出module path数组中的下一个目录作为基准查找,循环第1至5个步骤。
  7. 如果继续失败,循环第1至6个步骤,直到module path中的最后一个值。
  8. 如果仍然失败,则抛出异常。

何为核心? 重要的/不可缺的!

node.js作为一门跨平台服务器端编程语言,必然也有它的核心.

node.js继承了javascript 客户端语言该有的优势,同时摒弃了客户端javascript的一些缺点,比如在客户端javascript环境下,全局变量可以到处被定义,随意被覆盖,代码污染严重,所以node.js有了模块的概念,在模块里定义的全局变量如果没有被export ,那么此变量是私有的,只能在定义的模块是使用.

不用提醒我,我并没有跑题,正因为模块机制,导致node.js的一些核心模块也编译成各自独立的二进制文件,他们就放在 node 源代码中 lib 文件夹下.

为啥是二进制文件?你不用吃惊,reqire 可以引入 核心二进制模块,第三方模块,js文件,json文件及编译的好的c++模块(扩展名.node)

我们上面提到核心的就是重要的/不可缺的. 比如 'http' 模块, 'fs' 模块,这些内置的核心模块有优先载入的权限,也就是说,如果你自己创建了一个 http.js 的文件,然后通过require('http') 来引用的话,系统会优先把内置的 http 模块加载进来,而对于你这个同名的文件根本不理不睬.

如此看来,node的加载机制是有顺序的,我们不妨来理一下.

(1)首先加载核心模块

(2)试图在require 的名称后面加上.js 去搜索并加载.

(3)试图在require 的名称后面加上.json 去搜索并加载.

(4)试图在require 的名称后面加上 .node 去搜索并加装编译好的c++模块.

在加载文件模块时我们提到了搜索,如果搜索,node遵循什么规则?

常见的加载方式有3种类型

require('http');

require('./dbApi');

require('express');

上面列出的三种加载方式其实顺便也给出了加载顺序规则.

首先加载核心模块,不管有没有同名/同目录的情况下,核心模块优先加载.

注意:核心模块在node.js安装是已经被编译成二进制模块,程序启动时已经被自动加载到系统内存中,所以速度快,效率高.

其次按照相对路径/绝对路径加载文件模块(加载顺序,首先试图按照路径查找 .js 扩展名的文件,如果没有,试图按照路径查找 .json 扩展名的文件,如果还是没有,就按照路径查找 .node 扩展名的c++模块了)

注意:按照common.js 规范指示,模块加载过程中,模块名需要遵守小驼峰命名规则,且最好不带扩展名,但是上面三种文件中,因为有优先搜索规则,而文件io操作都是同步过程,也就是说,如果你要加载的是一个 config.json 文件,那么你的require('./config') 这样写导致系统首先在同层目录下搜索config.js 文件,直到系统搜索失败后才会继续搜索 config.json 文件,而这个由于IO同步操作会导致加载模块延迟,所以当你要加载 .json文件或者 .node 文件时,最好加上扩展名.

最后搜索 node_modules 目录下通过npm下载的第三方模块. 

注意:首次加载这类模块最慢,因为执行文件所在目录的node_mondel 文件夹下找不到时,会去父级node_mondel 文件夹里查找,如果还是找不到会去父级的父级node_mondel 文件夹里查找.......但是,只要首次加载成功后,node就会缓存起来,它缓存的是编译后的二进制模块,所以以后的加载速度和效率都的有保证的.

参考文献

https://www.cnblogs.com/520yang/articles/5039394.html

原文地址:https://www.cnblogs.com/zhoulixue/p/8757546.html