wenjinhua / my-blog

有关工作学习的知识点总结
3 stars 0 forks source link

详解node中引入模块的原理 #8

Open wenjinhua opened 3 years ago

wenjinhua commented 3 years ago

1. 模块机制

1.1 commonjs规范

  1. CommonJS规范为JavaScript制定了一个美好的愿景——希望JavaScript能够在任何地方运行。

  2. CommonJS对模块的定义十分简单,主要分为模块引用、模块定义和模块标识3个部分。

    • 模块引用:通过require方法引入模块
      var math = require('math');
    • 模块定义:通过exports对象导出当前模块的方法或变量(exports是module的属性)
      exports.add = function() {}
    • 模块标识:就是传递给require方法的参数,可以是以下几种形式(可以没有文件后缀名.js):
    • 符合小驼峰命名的字符串
    • 以.、..开头的相对路径
    • 绝对路径

      1.2 node的模块实现(node中引入模块的过程)

      node在实现中并非完全按照connonjs规范,而是对模块规范进行了一定的取舍,同时增加了少许自身的特性。

1. 在node中引入模块需要经历如下3个步骤:

2. node中,模块分为以下两类:

以下为详细的模块加载过程:

1.2.1 优先从缓存中加载

1.2.2 路径分析

因为标识符有几种形式,所以对于不同的标识符,模块的查找和定位有着不同程度的差异。模块分类如下:

1. 核心模块 核心模块的优先级仅次于缓存加载,它在node源代码编译过程中被编译成了二进制文件,加载速度最快。

2. 路径形式的文件模块 require会将路径转化为真实路径,并以真实路径作为索引,将编译执行后的结果放到缓存中,以使二次加载更快。

3. 自定义模块 node会逐个尝试模块路径中的路径,直到找到目标文件为止。

1.2.3 文件定位

1. 文件扩展名的分析:当传递给require的标识符不包括文件扩展名时,node会按照以下次序补足扩展名,依次尝试:

在尝试的过程中,node会调用fs模块同步阻塞式的判断文件是否存在。因为node是单线程的,所以这里是一个会引发性能问题的地方。(解决:在引入.json和.node文件时,加上扩展名)

2. 目录和包的处理:require通过分析文件扩展名后,可能得到的是一个目录,此时node会将目录当做一个包来处理

1.2.4 模块编译

定位到具体的文件后,node会新建一个模块对象,然后根据路径载入和编译。对于不同的扩展名,其载入方式如下:

注:每个编译成功的模块,都会将其文件路径作为索引缓存在Module._cache对象上,以提高二次引入的性能

1. js模块的编译 node对获取的js文件进行了头尾的包装,包装后的代码会通过vm原生模块的runInThisContext()执行

(function(exports, requiure, module, __filename, __dirname){
    ...
}

2. c/c++模块的编译 node调用process.dlopen()进行加载和执行,(dlope在windows和*nux平台通过libuv进行了兼容).node模块并不需要编译,因为他是编写c/c++模块之后编译生成的

3. json文件的编译

1.3 核心模块

1.3.1 js核心模块的编译过程

1.3.2 c/c++核心模块的编译过程

1. 内建模块

1.3.3 核心模块的引入流程

1.4 c/c++扩展模块