前言
学习编程,除了看网上的文章,还有就是阅读技术书籍,一般情况下,技术书籍会介绍的比较详细周全,因为一个成体系的技术,里面涉及的点总是方方面面,然后还会跟别的技术有所关联,但我有时候会碰到一种场景,就是突然需要急切的了解一个知识点,但是网上又没有搜到满意的答案时,这时候去看书,会花费的时间比较长,短时间的效益不明显。这时候我就特别希望有一些文章,能是直接把书里的重点给提炼出来,所以这就是我写这篇文章的初衷。
我这篇文章是在自己看了《深入浅出 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
) 这样的参数,这也就是刚才我们规范里所说的 exports
和 require
。
.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 实现包装或者向外导出。
所有的模块类型,是这么个依赖关系
C/C++ 拓展模块
我是一个前端工程师,其实对于 C/C++
不是那么的熟悉,这一部分的章节,看的不是很懂,我就简单说下我的理解。
node 里面有一个很关键的东西,叫 gyp,generate your project 跨平台的项目生成器
node 自身的源码是通过 gyp 编译的
简单来说,编译 C/C++
模块,在不同的系统需要不同的编译工具,在 mac 中是用 xcode,在 windows 用 Visual Studio
而且这个在 mac 系统下编译成的 .node 文件,复制到 windows 还不能接着使用,必须重新生成一次 .node 才行,这是因为 windows 和 *nix
的 .node 不相同,windows 下的 .node 实际是 .dll 文件,*uix
下的是 .so 文件。
想使用自定义的 C/C++
模块,就必须要进过三个步骤
编写
这个当然无可厚非,你总得会 C/C++
,然后编写代码是吧
编译
当然就是靠上边说的那个 gyp
加载
模块调用栈
NPM 与包
CommonJS 包规范是理论,NPM是其中的一种实践。
前后端共用模块
因为目前的 npm 的运行场景可能会在浏览器,也可能会在服务端,所以需要兼容浏览器端规范(AMD、CMD)和服务端规范(CommonJS)。
总结
文章的篇幅不长,一来可能是该章节内容不多,二来可能也是我的理解有限,例如对 C/C++
拓展模块方面不是很了解,不过没关系,过段时间,会再看一次本章节,希望到时候会有新的认识~