在核心模块中,有些模块全部由C/C++编写,有些模块则由C/C++完成核心部分,其它部分则由JavaScript实现包装或向外导出,以满足性能需求。后面这种C++模块主内完成核心,JavaScrpt主外实现封装的模式是Node能够提高性能的常见方式。通常,脚本语言的开发速度优于静态语言,但是其性能则弱于静态语言。而Node的这种复合模式可以在开发速度和性能之间找到平衡点。
这里我们将那些由纯C/C++编写的部分统一称为内建模块,因为它们通常不被用户直接调用。Node的buffer、crypto、evals、fs、os等模块都是部分通过C/C++编写的。
## 1.内建模块的组织形式
在Node中,内建模块的内部结构定义如下:
~~~
struct node_module_struct{
int version;
void *dso_handle;
const char *filename;
void (*register_func) (v8::Handle<v8::Object> traget);
const char *modname;
};
~~~
每一个内建模块在定义之后,都通过`NODE_MODULE`宏将模块定义到 `node` 命名空间中,模块的具体初始化方法挂载为结构的` register_func`成员:
~~~
#define NODE_MODULE(modname, regfunc) \
extern "C" { \
NODE_MODULE_EXPORT node::node_module_struct modname ## _module = \
{ \
NODE_STANDARD_MODULE_STUFF, \
regfunc, \
NODE_STRINGIFY(modname) \
}; \
} \
~~~
`node_extensions.h`文件将这些散列的内建模块统一放进了一个叫`node_module_list`的数组中,这些模块有:
* node_buffer
* node_crypto
* node_evals
* node_fs
* node_http_parser
* node_os
* node_zlib
* node_timer_wrap
* node_tcp_wrap
* node_udp_wrap
* node_pipe_wrap
* node_cares_wrap
* node_tty_wrap
* node_process_wrap
* node_fs_event_wrap
* node_signal_watcher
这些内建模块的取出也十分简单。Node提供了`get_builtin_module()`方法从`node_module_list`数组中取出这些模块。
内建模块的优势在于:首先,它们本身由C/C++编写,性能行优于脚本语言;其次,在进行文件编译时,它们被编译进二进制文件。一旦Node开始执行,它们被直接加载进内存中,无需再次做标识符定位、文件定位、编译等过程,直接就可执行。
## 2.内建模块的导出
在Node的所有模块类型中,存在着如下图所示的一种依赖层级关系,即文件模块可能会依赖核心模块,核心模块可能会依赖内建模块。
![](https://box.kancloud.cn/2016-08-25_57bea5ead51f0.png)
通常,不推荐文件模块直接调用内建模块。如需调用,直接调用核心模块即可,因为核心模块中基本都封装了内建模块。那么内建模块是如何将内部变量或方法导出,以供外部JavaScript核心模块调用呢?
Node在启动时,会生成一个全局变量process,并提供Binding()方法来协助加载内建模块。 Binding()的实现代码在src/node.cc中,具体如下所示:
~~~
static Handle<Value> Binding(const Arguments& args){
HandleScope scope;
Local<String> module = args[0]->ToString();
String::Utf8Value module_v(module);
node_module_struct* modp;
if(binding_cache.IsEmpty()){
binding_cache=Presistent<Object>::New(Object::New());
}
Local<Object> exports;
if(binding_cache->Has(module)){
exports = binding_cache->Get(module)->ToObject();
return scope.Close(exports);
}
// Append a string to process.moduleLoadList
char buf[1024];
snprintf(buf, 1024, "Binding %s", *module_v);
uint32_t l = module_load_list->Length();
module_load_list->Set(l, String::New(buf));
if((modp=get_builtin_module(*module_v)) != NULL){
exports = Object::New();
modp->register_func(exports);
binding_cache->Set(module, exports);
}else if(!strcmp(*module_v,"constants")){
exports = Object::New();
DefineConstants(exports);
binding_cache->Set(module,exports);
#ifdef __POSIX__
}else if(!strcmp(*module_v,"io_watcher")){
exports = Object::New();
IOMatcher::Initialize(exports);
binding_cache->Set(module, exports);
#endif
}else if (!strcmp(*module_v,"natives")){
exports = Object::New();
DefineJavaScript(exports);
binding_cache->Set(module, exports);
}else{
return ThrowException(Exception::Error(String::New("No such module")));
}
return scope.Close(exports);
}
~~~
在加载内建模块时,我们先创建一个exports空对象,然后调用get_builtin_module()方法取出内建模块对象,通过执行register_func()填充exports对象,最后将exports对象按模块名缓存,并返回给调用方完成导出。
这个方法不仅可以导出内建方法,还能导出一些别的内容。前面提到的JavaScript核心文件被转换为C/C++数组存储后,便是通过process.binding('natives')取出放置在NativeModule._source中的:
~~~
NativeModule._source = process.binding('natives');
~~~
该方法将通过js2c.py工具转换出的字符串数组取出,然后重新转换为普通字符串,以对JavaScript核心模块进行编译和执行。
- 目录
- 第1章 Node 简介
- 1.1 Node 的诞生历程
- 1.2 Node 的命名与起源
- 1.2.1 为什么是 JavaScript
- 1.2.2 为什么叫 Node
- 1.3 Node给JavaScript带来的意义
- 1.4 Node 的特点
- 1.4.1 异步 I/O
- 1.4.2 事件与回调函数
- 1.4.3 单线程
- 1.4.4 跨平台
- 1.5 Node 的应用场景
- 1.5.1 I/O 密集型
- 1.5.2 是否不擅长CPU密集型业务
- 1.5.3 与遗留系统和平共处
- 1.5.4 分布式应用
- 1.6 Node 的使用者
- 1.7 参考资源
- 第2章 模块机制
- 2.1 CommonJS 规范
- 2.1.1 CommonJS 的出发点
- 2.1.2 CommonJS 的模块规范
- 2.2 Node 的模块实现
- 2.2.1 优先从缓存加载
- 2.2.2 路径分析和文件定位
- 2.2.3 模块编译
- 2.3 核心模块
- 2.3.1 JavaScript核心模块的编译过程
- 2.3.2 C/C++核心模块的编译过程
- 2.3.3 核心模块的引入流程
- 2.3.4 编写核心模块
- 2.4 C/C++扩展模块
- 2.4.1 前提条件
- 2.4.2 C/C++扩展模块的编写
- 2.4.3 C/C++扩展模块的编译
- 2.4.2 C/C++扩展模块的加载
- 2.5 模块调用栈
- 2.6 包与NPM
- 2.6.1 包结构
- 2.6.2 包描述文件与NPM
- 2.6.3 NPM常用功能
- 2.6.4 局域NPM
- 2.6.5 NPM潜在问题
- 2.7 前后端共用模块
- 2.7.1 模块的侧重点
- 2.7.2 AMD规范
- 2.7.3 CMD规范
- 2.7.4 兼容多种模块规范
- 2.8 总结
- 2.9 参考资源
- 第3章 异步I/O
- 3.1 为什么要异步I/O
- 3.1.1 用户体验
- 3.1.2 资源分配
- 3.2 异步I/O实现现状
- 3.2.1 异步I/O与非阻塞I/O
- 3.2.2 理想的非阻塞异步I/O
- 3.2.3 现实的异步I/O
- 3.3 Node的异步I/O
- 3.3.1 事件循环
- 3.3.2 观察者
- 3.3.3 请求对象
- 3.3.4 执行回调
- 3.3.5 小结
- 3.4 非I/O的异步API
- 3.4.1 定时器
- 3.5 事件驱动与高性能服务器