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目录,其速度最快。其余流程如下:
- 从module path数组中取出第一个目录作为查找基准。
- 直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找。
- 尝试添加.js、.json、.node后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。
- 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
- 尝试查找该文件,如果存在,则结束查找。如果不存在,则进行第3条查找。
- 如果继续失败,则取出module path数组中的下一个目录作为基准查找,循环第1至5个步骤。
- 如果继续失败,循环第1至6个步骤,直到module path中的最后一个值。
- 如果仍然失败,则抛出异常。
何为核心? 重要的/不可缺的!
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