Node.js源码研究之模块组织加载

开发 前端
粗略研究了一下node.js源码,它有8000行C++代码,2000行javascript代码,来看看js和C++间是怎么组织连接起来,以及各个模块是怎样互相调用的。

粗略研究了一下node.js源码,它有8000行C++代码,2000行javascript代码,来看看js和C++间是怎么组织连接起来,以及各个模块是怎样互相调用的。

本文使用的node.js版本是0.4.8,可以在https://github.com/joyent/node/tree/v0.4.8这里看到源码。

js2c.py

node.js使用了V8附带的js2c.py工具把所有内置的js代码转换成C++里的数组,生成node_natives.h直接include到程序中,成了C++源码的一部分。这样做能提高内置js模块的编译效率。

node.js里内置的javascript包括了主程序src/node.js和模块程序lib/*.js,通过js2c.py让每一个js文件都生成一个源码数组,存放在build/src/node_natives.h里,node_natives.h在node.js编译后才会生成(编译的脚本wscript中调用了js2c.py),可以看到大致的结构如下:

  1. namespace node {  
  2. const char node_native[] = {47, 47, 32, 67, 112 ......}  
  3. const char console_native[] = {47, 47, 32, 67, 112 ......}  
  4. const char buffer_native[] = {47, 47, 32, 67, 112 ......}  
  5. .....  
  6. }  
  7. struct _native { const char* name; const char* source; size_t source_len;};  
  8. static const struct _native natives[] = {  
  9. "node", node_native, sizeof(node_native)-1 },  
  10. "dgram", dgram_native, sizeof(dgram_native)-1 },  
  11. "console", console_native, sizeof(console_native)-1 },  
  12. "buffer", buffer_native, sizeof(buffer_native)-1 },  
  13. ....  

这个文件被包含在node_javascript.cc里,node_javascript.cc提供了两个接口:

MainSource() 处理node_native源码返回v8::Handle类型的数据可供编译。

DefineJavaScript(target) 把其他所有模块源码变成v8::Handle类型后加载到传入的target对象上。

所有的js模块都被转换成了C数组,接下来看看它们怎么执行和互相调用。

执行js主程序/传递process

先看看node.js的底层C++传递给javascript的一个变量process,在一开始运行node.js时,程序会先配置好process

  1. Handleprocess = SetupProcessObject(argc, argv); 

然后把process作为参数去调用js主程序src/node.js返回的函数,这样process就传递到javascript里了。

  1. //node.cc  
  2. //通过MainSource()获取已转化的src/node.js源码,并执行它  
  3. Local f_value = ExecuteString(MainSource(), IMMUTABLE_STRING("node.js"));  
  4.  
  5. //执行src/node.js后获得的是一个函数,从node.js源码可以看出:  
  6. //node.js  
  7. //(function(process) {  
  8. // global = this;  
  9. // ....  
  10. //})  
  11. Local f = Local::Cast(f_value);  
  12.  
  13.  
  14. //创建函数执行环境,调用函数,把process传入  
  15. Localglobal = v8::Context::GetCurrent()->Global();  
  16. Local args[1] = { Local::New(process) };  
  17. f->Call(global, 1, args); 

C++模块

node.js的模块除了lib/*.js里用js语言编写的以外,还有一些使用C++编写,像os/stdio/crypto/buffer等。这些模块都通过node.h提供的NODE_MODULE方法存储在变量_module里。node_extensions.cc提供了get_builtin_module(name)接口获取这些模块。

process.binding/C++模块加载

process提供的一个获取模块的接口是binding,它的实现Binding()函数可以在node.cc找到。

  1. Persistent binding_cache;  
  2. static Handle Binding(const Arguments& args) {  
  3. HandleScope scope;  
  4. Local module = args[0]->ToString();  
  5. String::Utf8Value module_v(module);  
  6. node_module_struct* modp;  
  7.  
  8. if (binding_cache.IsEmpty()) {  
  9. binding_cache = Persistent::New(Object::New());  
  10. }  
  11. Local exports;  
  12. if (binding_cache->Has(module)) {  
  13. exports = binding_cache->Get(module)->ToObject();  
  14.  
  15. else if ((modp = get_builtin_module(*module_v)) != NULL) {  
  16. exports = Object::New();  
  17. modp->register_func(exports);  
  18. binding_cache->Set(module, exports);  
  19.  
  20. else if (!strcmp(*module_v, "constants")) {  
  21. exports = Object::New();  
  22. DefineConstants(exports);  
  23. binding_cache->Set(module, exports);  
  24.  
  25. #ifdef __POSIX__  
  26. else if (!strcmp(*module_v, "io_watcher")) {  
  27. exports = Object::New();  
  28. IOWatcher::Initialize(exports);  
  29. binding_cache->Set(module, exports);  
  30. #endif  
  31.  
  32. else if (!strcmp(*module_v, "natives")) {  
  33. exports = Object::New();  
  34. DefineJavaScript(exports);  
  35. binding_cache->Set(module, exports);  
  36.  
  37.  
  38. else {  
  39. return ThrowException(Exception::Error(String::New("No such module")));  
  40. }  
  41. return scope.Close(exports);  

从源码可以看到,调用process.binding时,先看缓存里是否已经存在此模块,不存在再调用get_builtin_module查找C++内置模块,找到的话获取后绑定在exports上,在最后返回exports。

此外还有针对其他模块的特殊处理,其中natives模块就是调用上文提到的DefineJavaScript(exports)接口获取到所有内置的js模块绑定在exports上。

现在在js上需要调用C++提供的模块只需要调用process.binding就行了,例如

  1. var stdio = process.binding("stdio"

js模块加载

src/node.js上实现了一个NativeModule对象用于管理js模块,它通过调用process.binding(“natives”)把所有内置的js模块放在NativeModule._source上,并提供require接口供调用。在require里会给代码加一层包装,把一些变量传给这个模块。

  1. NativeModule.wrapper = [  
  2. '(function (exports, require, module, __filename, __dirname) { ',  
  3. '\n});' 
  4. ]; 

再用process提供的其中一个js编译接口process.runInThisContext执行代码。

  1. var fn = runInThisContext(source, this.filename, true);  
  2. fn(this.exports, NativeModule.require, thisthis.filename); 

于是在主程序src/node.js上可以调用NativeModule.require(“net”)去加载net模块,在lib/*.js的各个js模块里能通过调用传进来的require()去加载其他内置js模块。

总结流程

粗略总结一下加载模块的流程:

加载C++模块(以stdio为例):

process.binding(“stdio”) -> get_builtin_module(“stdio”) -> _module -> NODE_MODULE(node_stdio, node::Stdio::Initialize)(定义)

加载js模块(以net为例)

require(“net”) -> NativeModule.require(“net”) -> process.binding(“natives”)["net"] -> DefineJavaScript() -> natives[] -> node_natives.h

原文:http://cnodejs.org/blog/?p=1280

【编辑推荐】

  1. 揭秘Node.js事件
  2. Node.js初探之与Mysql的交互
  3. Node.js初探之hello world
  4. Node.js入门之神秘的服务器端JavaScript
  5. 什么是Node.js?
责任编辑:陈贻新 来源: cnodejs.org
相关推荐

2019-12-17 11:40:44

Node.js模块前端

2020-08-31 15:00:17

Node.jsrequire前端

2021-09-26 05:06:04

Node.js模块机制

2020-04-15 15:48:03

Node.jsstream前端

2011-09-08 10:41:12

Node.js

2019-12-10 10:23:57

Node.jsCluster前端

2023-06-30 23:25:46

HTTP模块内存

2013-11-01 09:34:56

Node.js技术

2015-03-10 10:59:18

Node.js开发指南基础介绍

2011-12-09 11:16:48

Node.js

2011-09-08 10:32:27

Node.js

2011-09-08 13:46:14

node.js

2011-09-02 14:47:48

Node

2011-11-01 10:30:36

Node.js

2011-09-09 14:23:13

Node.js

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2021-01-26 08:07:44

Node.js模块 Async

2021-12-25 22:29:57

Node.js 微任务处理事件循环

2023-06-20 19:35:00

Node.js工具
点赞
收藏

51CTO技术栈公众号