摘要:Node.js源码有8000行C++代码,2000行javascript代码,本文来看看js和C++间是怎么组织连接起来,以及各个模块是怎样互相调用的。希望阅读本篇文章以后大家有所收获,帮助大家对Node.js的理解更加深入。
粗略研究了一下node.js源码,它有8000行C++代码,2000行javascript代码,来看看js和C++间是怎么组织连接起来,以及各个模块是怎样互相调用的。
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. .... 14. }
这个文件被包含在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
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); 42. }
从源码可以看到,调用process.binding时,先看缓存里是否已经存在此模块,不存在再调用get_builtin_module查找C++内置模块,找到的话获取后绑定在exports上,在最后返回exports。
此外还有针对其他模块的特殊处理,其中natives模块就是调用上文提到的DefineJavaScript(exports)接口获取到所有内置的js模块绑定在exports上。
现在在js上需要调用C++提供的模块只需要调用process.binding就行了,例如
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, this, this.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
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注WEB前端Node.js频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号