本文介绍下node的模块机制
node中模块是如何加载的?
也就是
let fs = require('fs'), 这句代码node在背后干了些什么?
node要干三个事情:
- 1: 路径分析
- 2: 文件定位
- 3: 编译执行
其中node的加载有一个缓存的机制,将所有已经加载过的模块进行缓存,缓存的是编译和执行之后的对象。
核心模块在Node源代码编译时候已经编译成了二进制代码。加载速度最快。
路径分析及文件定位
如果require后面是核心模块,分析完标识符发现是核心模块后,直接定位到内存中,因此比普通的文件模块从磁盘中一处一处查找快很多。
如果require后面是路径,node就将其转为真实路径,然后去定位文件;
如果require后面不是核心模块,也不是路径,而是一个包名字,那么其会在node_modules中找,找不到就去上一级的node_modules找,找到之后找package.json读里面main属性指定的文件名。
路径形式的文件模块,node会将路径转为真实路径,并在编译后,将编译的对象放在缓存中(以文件路径为key);核心模块也会进行缓存,只是和文件模块缓存位置不是一个。
编译执行
1:js文件的编译执行
在Node中,每一个文件模块都是一个对象。 在编译过程中,node对获取的js文件内容进行了头尾包装:
function(exports, require, module,_filename,_dirname){
//实际的js文件里的代码
}
这样每个模块文件都进行了作用于隔离,并且你在文件模块能有require,_filename这些变量。
被包装好的代码,执行之后,模块的exports属性被返回给调用方,这样调用方就可以访问exports属性上的任何方法和属性,但是模块中的其余变量或者属性不可直接被调用。
2: c/c++ 模块的编译
Node调用process.dlopen()方法进行加载和执行。实际上.node文件不需要编译,因为他已经是C/C++编译之后的产物,所以只需要加载和执行。在执行的过程中,模块的exports对象和.node模块产生关联,然后返回给调用者。
3: JSON 文件的编译
最简单,node使用fs模块读取文件,然后调用JSON.parse得到对象,然后赋值给exports,以供外部使用。
4:核心模块
核心模块与开发者编写的文件模块区别:
- 路径分析时候,会优先判断是不是核心模块。
- 启动Node进程时候,部分核心模块代码被直接加载进内存中,而文件模块是在运行时候动态加载的。
- 引入核心模块时候,node分析后直接定位到内存中,比普通文件从磁盘中一处一处查找快很多。
- 加载一次之后,缓存的位置不同:核心模块缓存在
NativeModule._cache对象上,普通模块缓存在Module._cache对象上。
node中模块的实现
node中模块的实现,答案也就是上面node是如何编译执行的。即require、exports、module流程。