node之模块

172 阅读3分钟

本文介绍下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是如何编译执行的。即requireexportsmodule流程。