一,模块加载机制
Node.js 的模块加载对用户来说十分简单,只需要调用require即可,但是内部机制较为复杂。
模块的类型
Node.js的模块可以分为两大类,一类是核心模块,另一类是文件模块。核心模块就是Node.js标准的API中提供的模块,如fs,http,net,vm等,这些都是由Node.js官方提供的模块,编译成了二进制代码,我们可以通过require获取核心模块,例如:require("fs")。核心模块拥有最高的加载优先级,换言之如果有模块与其命名冲突,Node.js总会加载核心模块。
文件模式则是存储为单独的文件(或文件夹)的模块,可能是JavaScript代码,JSON或编译好的C/C++代码。文件模块的加载方法相对复杂,但十分灵活,尤其是和npm结合使用时,在不显式指定文件模块扩展名的时候,node.js会自动试图加上.js和.json和.node扩展名。.js是JavaScript代码,.json是JSON格式的文件,.node编译好的C/C++代码。
按路径加载模块
文件模块的加载有两种方式,一种是按路径加载,一种是查找node_modules文件夹。
如果require()参数以"/"开头,那么就以绝对路径的方式查找模块名称,例如:require("/home/mgt360124/module")将会尝试按照优先级依次加载/home/mgt360124/module.js,/home/mgt360124/module.json,/home/mgt360124/module.node。
如果require参数以“./”或“../”开头,那么则以相对路径的方式来查找模块。这种方式在应用中是最常见的,例如:require("./hello")来加载同一文件夹下的hello.js。
通过查找node_modules目录加载模块
如果require参数不以“/”,“./” 或"../"开头,并且该模块又不是核心模块的话,就要通过查找node_modules 加载模块了,使用npm获取的包通常是以这种方式加载的。
在某个目录下执行命令 npm install express,出现了node_modules的目录,里面的结构是:
在node_modules目录的外面一层,可以直接使用require("express");来代替require("./node_modules/express")。这是Node.js模块加载的一个重要特性,通过查找node_modules目录来加载模块。
当require遇到一个既不是核心模块,又不是以路径形式表示的模块文件时,会试图在当前目录下的node_modeules目录中查找是不是有这个模块,没有的话就向当前目录的上一层中的node_modules目录中查找,反复执行这一过程,直到遇到根目录为止,例如:要在/home/mgt360124/data/reg.js中使用require("login.js")命令。Node.js会依次查找:
/home/mgt360124/data/node_modules/bar.js
/home/mgt360124/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
因为通常一个工程内会有一些子目录,当子目录内的文件需要引入工程共同依赖的模块时,就需要向父目录上追溯了,比如工程目录的结构为:
在project目录下的app.js中使用require("express"),而且可能要在controllers的子目录下的index_controller.js中也使用require("express"),那么这时需要向父目录上溯一层才能找到node_modules中找到express。
加载缓存
Node.js 模块不会被重复加载,这是因为Node.js通过文件名缓存所有加载过的文件模块,所以以后再访问到时就不会重新加载了,Node.js是根据实际文件名缓存的,而不是require()提供的参数缓存的,也就是说即使通过require("express")和require("./node_modules/express")加载两次,也不会重新加载,因为尽管两次参数不同,但是解析到的文件却是同一个文件。
加载顺序
总结使用require(some_module)时的加载顺序
(1)如果some_module是一个核心模块,直接加载,结束。
(2)如果 some_module以“/”,"./",“../”开头,按路径加载some_module结束。
(3)假设当前目录为current_dir ,按路径加载current_dir/node_modules/node_module,如果加载成功,结束。如果加载失败,令current_dir为其父目录。重复这一过程,知道遇到根目录,抛出异常,结束。