什么是 nodejs 的模块机制

91 阅读5分钟

前言

学习编程,除了看网上的文章,还有就是阅读技术书籍,一般情况下,技术书籍会介绍的比较详细周全,因为一个成体系的技术,里面涉及的点总是方方面面,然后还会跟别的技术有所关联,但我有时候会碰到一种场景,就是突然需要急切的了解一个知识点,但是网上又没有搜到满意的答案时,这时候去看书,会花费的时间比较长,短时间的效益不明显。这时候我就特别希望有一些文章,能是直接把书里的重点给提炼出来,所以这就是我写这篇文章的初衷。

我这篇文章是在自己看了《深入浅出 nodejs》的第二章模块机制后写的,一可以作为我的读书笔记吧,二来如果有人想要快速了解 node 模块的一些重要知识点也可以看看。

CommonJS 模块规范

规范这块比较简单。

CommonJS 的模块定义分三个部分,模块引用、模块定义和模块标识。

模块引用

我们常用的 require(xxx)

模块定义

就是 exoprts.xxx

模块标识

require(xxx) 里面的 xxx

Node 模块实现

node 的模块主要有两类,node 提供的核心模块,以及用户编写的文件模块。
核心模块就是 C/C++ 写好的,编译过程中编译成了二进制文件,进程启动的时候直接加入内存。
文件模块就是用户开发的 js 模块
引入模块要经历三个步骤:

  • 路径分析
  • 文件定位
  • 编译执行 只有文件模块需要,核心模块不需要经历文件定位和编译执行,路径分析时也会优先判断。

node 会对引入的模块进行缓存,所以加载优先级是:

  • 核心文件的缓存
  • 文件模块的缓存
  • 自定义模块的缓存
  • 核心文件
  • 文件模块
  • 自定义模块 我们来看看 node 如何实现刚才所说的规范的。
路径分析
  • 核心模块
  • 路径形式和非路径形式的文件模块
  • 自定义模块 沿路径向上逐级递归,查找 node_modules 目录,直到根目录下
文件定位

如果加载文件模块时没有指定拓展名,node 会按如下顺序进行尝试

  • .js 通过 fs 模块同步读取后执行
  • .node C/C++ 编写的扩展文件 通过 dlopen 方法加载最后编译生成的文件
  • .json fs 模块同步读取到后 通过 JSON.parse 解析返回结果 其余扩展名都被当成 .js 文件
编译执行

node 每个文件模块都是一个对象,且是在运行时动态加载的。

.js 的编译

编译的时候,会把代码存储在 vm.Script (vm 是 node 的核心模块,可以理解为是自带的)中, 使用 fs 模块同步读取,然后通过 vm.runInThisContext 来执行,执行后得到一个 function,给这个 function 最后传入 (exports, require, module, __filename, __dirname) 这样的参数,这也就是刚才我们规范里所说的 exportsrequire

.node 的编译

其实没有编译,就是加载和执行。

.json

fs 模块同步读取到后 通过 JSON.parse 解析得到结果,赋值给 exports。

核心模块

刚才说过,node 会对引入过的模块进行缓存,而 node 每个文件模块都是一个对象,Module

function Module(id, parent) { 
    this.id = id;
    this.exports = {}; this.parent = parent;
    if (parent && parent.children) {
         parent.children.push(this);
    }
    this.filename = null; 
    this.loaded = false; 
    this.children = [];
}

文件模块缓存在 Module._cache 上,其实核心模块也是一个对象 NativeModule

function NativeModule(id) { 
    this.filename = id + '.js'; 
    this.id = id;
    this.exports = {};
    this.loaded = false;
}

核心模块的缓存是在 NativeModule._cache

核心模块分两种,纯由 C/C++ 编写的(也叫内建模块),和由 C/C++ 编写核心部分,其余由 js 实现包装或者向外导出。
所有的模块类型,是这么个依赖关系

image.png

C/C++ 拓展模块

我是一个前端工程师,其实对于 C/C++ 不是那么的熟悉,这一部分的章节,看的不是很懂,我就简单说下我的理解。
node 里面有一个很关键的东西,叫 gyp,generate your project 跨平台的项目生成器
node 自身的源码是通过 gyp 编译的
简单来说,编译 C/C++ 模块,在不同的系统需要不同的编译工具,在 mac 中是用 xcode,在 windows 用 Visual Studio

image.png

而且这个在 mac 系统下编译成的 .node 文件,复制到 windows 还不能接着使用,必须重新生成一次 .node 才行,这是因为 windows 和 *nix 的 .node 不相同,windows 下的 .node 实际是 .dll 文件,*uix 下的是 .so 文件。

想使用自定义的 C/C++ 模块,就必须要进过三个步骤

编写

这个当然无可厚非,你总得会 C/C++,然后编写代码是吧

编译

当然就是靠上边说的那个 gyp

加载

image.png

模块调用栈

image.png

NPM 与包

CommonJS 包规范是理论,NPM是其中的一种实践。

image.png

前后端共用模块

因为目前的 npm 的运行场景可能会在浏览器,也可能会在服务端,所以需要兼容浏览器端规范(AMD、CMD)和服务端规范(CommonJS)。

总结

文章的篇幅不长,一来可能是该章节内容不多,二来可能也是我的理解有限,例如对 C/C++ 拓展模块方面不是很了解,不过没关系,过段时间,会再看一次本章节,希望到时候会有新的认识~