介绍
上篇文章介绍了模块机制中的文件模块(直通链接),本篇文章继续接着介绍node模块机制中剩余的模块类型:
- 核心模块
- 扩展模块
注:本文是学习《深入浅出Node.js》一书的学习笔记,所以参考源码与书上版本一致。(版本:v0.10.13-release)
核心模块
什么是核心模块
区别于文件模块等第三方导入模块,核心模块就是直接包含在node源码中,为用户提供服务的模块。
当然这些模块也需要导入的,想了解都有哪些模块,直接进入官网,找到Docs文档,里面介绍的模块都是核心模块(链接:核心模块介绍)。
简单列举几个比较常用的核心模块:
- fs:提供对文件的读写等操作
- http:提供了搭建本地服务器的API
- path:提供文件路径操作的API
- os:操作系统相关信息的API
- url:提供了操作URL信息的API
核心模块导入流程
核心模块的使用官网已经非常详细了,我也没什么好介绍的,今天的重点是核心模块在Node源码中是以什么形式存在的,以及核心模块的导入流程。
Node源码中的核心模块
核心模块一般分为两部分:
- C++编写的内建模块(核心实现)
- JavaScript编写的封装模块
其中C++部分的代码存放在src/
目录下,而JavaScript的代码存放在lib/
下。
一般我们调用的都是JS编写的封装模块,在对Node源码不是特别了解的情况下,不建议直接调用C++编写的内建模块(调用方法也就不告诉你了)。
导入流程
导入流程主要分为如下几个步骤:
- js代码转存
- 运行时从内存中取出内容
- 编译执行
- 对象缓存和返回
看不懂没关系,下面详细介绍。
js代码转存
node编译过程中,并不会对js部分的代码进行编译操作,而是调用tools/js2c.py
工具将js代码转换成C++中的字符串数组,存放在node_natives.h
文件中,手动执行后的情况如下:
看代码就知道,这一步其实做了个很简单的事情,把js文件内容console.log("hello");
变成ASCII码放在$filename_native
数组中,这样node源码编译时,js文件的内容也就以字符串的形式编译进了可执行文件中。
取出文件
知道内容被存到node_natives.h
文件中,那么取出的代码就很好找了,首先找到调用文件的地方,运气很好,只有一个文件node_javascript.cc
,而且确实是获取js文件的代码,函数很简单,直接贴源码了
1 | void DefineJavaScript(v8::Handle<v8::Object> target) { |
一共几行代码,功能就是把想要的js文件内容取出,这里有个条件,是除了node_native
模块,这里其实就是node.js
文件,与其他js文件不同的是它在src/
下,这个文件很重要,这里先不说明,我们继续往下走。
以前版本的代码就是简单,这里我们查使用函数DefineJavaScript()
的地方,发现也只有一个地方,就是我们的node.cc
文件(题外话,这个文件就是定义着我们的全局变量process
的地方),而使用的函数为Binding()
,以下为源码内容:
1 | static Handle<Value> Binding(const Arguments& args) { |
去掉无关的代码,留下我们的重要关注内容,从这部分代码可以得出,调用process.Binding('natives')
就可以取出内存中的js代码,而这个引用就在我们刚刚谈到的node.js
中,
1 | NativeModule._source = process.binding('natives'); |
重点出来了,之前我们讲过,文件模块操作的对象为Module
,那么我们核心模块的对象就是NativeModule
,
编译&缓存
当我们对一个核心模块引用时,实际上就是运行了下列代码:
1 | NativeModule.require = function(id) { |
一下子放这么多代码实在是不应该,但是这部分代码简单而道出了核心模块导入的本质,实在不忍心割掉一点点,每个函数都对功能添加了注释,请务必仔细研读。
另外谈一点,核心模块的js代码因为是从内存中取出的,相比于从磁盘取出的文件模块加载速度肯定要快上很多。
核心模块至此就结束了,流程简单来说就是:编译node时将核心js代码变成c++中的字符串数组,引用的时候从内存中取出并进行编译,然后缓存放到NativeModule._cache
中并返回编译后的对象,如果你看过文件模块的介绍,应该并不难掌握。
扩展模块
终于讲完了核心模块,希望你已经掌握,下面来讲一种特殊的文件模块,扩展模块。
什么是扩展模块
亘古不变,我们先来说下是什么。
扩展模块是用C/C++代码预编译后得到的.node
文件,代码中调用process.dlopen()
进行加载执行。显然这种方式性能要高于js编写的文件模块,而且在需要大量的位运算操作的场景中,C/C++的性能要优于JS。
JavaScript的位运算参照Java的位运算实现,但是Java位运算是在
int
型数字的基础上进行的,而JavaScript中只有double
型的数据类型,在进行位运算的过程中,需要将double
型转换为int
型,然后再进行。所以,在JavaScript层面上做位运算的效率不高。(节选自书中)
这里你可能要说,那大家都去写扩展模块算了。世界上没有完美的东西,扩展模块也有他的缺点,当然,C/C++语言的学习和开发难度肯定是第一点,不然脚本语言也不会盛行于世,其次就是不再支持跨平台,对于*nix和windows平台的差异如下:

这里也为我们展现了扩展模块的编译和导入流程。
所以在需要跨平台的应用上,应该对扩展模块的使用要小心,
实现一个自己的扩展模块
是的,这里应该有一个实现,不过这部分我并没有掌握清楚,书中的例子因为版本古老,编译失败了,所以这里也就不展示了,给自己留个坑,也希望感兴趣的小伙伴可以研究实现一个自己的扩展模块,加油,你们是最胖的!!!
总结
模块深入学习打开了我对node源码解读的开端,尽管代码是老的版本,但依旧能从中学到很多,这一系列文章是我读《深入浅出Node.js》一书的学习笔记,也希望自己能够成功读完,并将学习后的知识分享给大家。