多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
在核心模块中,有些模块全部由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核心模块进行编译和执行。