Node.js或Node是JavaScript的运行时(runtime), 也就是说给Node一段JavaScript 代码,它就能运行。比如:index.js中写如下代码,
const obj = { sum(a, b) { return a + b; } } obj.sum(2, 3);
node index.js,它就顺利执行,虽然没有什么结果。那Node是如何做到的? 首先看一下,如果能够执行这段JavaScript 代码,都需要什么?
1, 需要编译或解释,因为JavaScript 是高级语言,计算机根本不认识,需要编译或解释成计算机能认识的机器语言。
2, 要认提供数据类型和操作符,比如数据类型,对象和函数,还有+等操作符,这样在程序中才能使用。
3, 在栈内存来分配变量,如a, b, 还要通过call stack来管理函数的执行。
4, 在堆内存来创建和管理对象,如obj,最好提供垃圾回收机制。
突然发现,浏览器中的JavaScript引擎就是做这些事情的,那能不能把浏览器引擎集成到Node中?可以。Chrome的V8 就很好集成,因为V8引擎是一个用C++写的开源项目,它可以嵌入或集成到任何的C++项目中,只要你的C++ 项目包含V8 作为依赖库,你就能用V8的API 来编译和运行JavaScript 代码。只要Node中集成V8引擎,它就能编译和运行JavaScript 代码。
编译和运行JavaScript代码的问题解决了,但这时,你也发现了一个问题,怎么输出程序的计算结果呢?V8中,或JavaScript 中并没有提供输入输出的方法,没有办法和外界进行交互?JavaScript并没有操作计算机底层的能力,那就只能用另外一种语言实现,C++. 那就用JavaScript来调用C++,容易实现吗?也可以,V8可以暴露C++的代码给JavaScript, 从而可以使用JavaScript来调用C++的方法。这是通过V8 template 实现的。只要JavaScript能调用C++的代码,那就能实现JavaScript不能提供的功能,比如,文件读写,网络编程 。Node 中提供了一个process 全局对象,它有一个stdout属性,stdout 有一个方法,可以输出内容
const obj = { sum(a, b) { return a + b; } } const result = obj.sum(2, 3); process.stdout.write(result.toString());
文件读写和网络编程呢?那就用到了另外一个库 --- libuv, 它也是用C++ 写的,所以JavaScript 可以调用它的方法来实现文件读写和网络编程等。但它是一个异步的, 事件驱动的I/O 库。那就说到Node的异步编程。
那异步代码怎么处理呢?可能你已经想到了,事件循环和事件队列,浏览器中也是这样实现的。
setTimeout(function() { const foo = 2 + 2; console.log("Hello World!"); }, 1000);
当V8 编译和运行JavaScript代码的时候,同步代码执行完毕,它就启动事件循环,来轮询事件队列中的事件,如果有事件,它就放V8的call stack 中,V8 就会执行代码。事件循环,只是一个循环,它只循环事件队列中,有没有可以执行的事件,如果有,它就放到V8 call back中,然后V8 执行代码。事件循环不会执行代码。比如上面这段代码,1s 过后,Node就把回调函数放到事件队列中,事件循环轮询到有一个可以执行的事件(就是回调函数),把它拿出来,给到V8, V8 就可以执行函数,分配变量,执行完毕后,垃圾回收。
文件的读写也是同样的道理
const fs = require('fs'); const obj = { sum(a, b) { return a + b; } } const result = obj.sum(2, 3); fs.writeFile("result.txt", result, (err) => { if (err) console.log(err); else { console.log("File written successfully"); } });
文件写入成功,回调函数放到事件队列中,事件循环把它拿出来给V8, 进行执行。
如果深究Node的事件循环,那就有点复杂了。由于Node非常多的异步事件,它们到底怎么执行,执行顺序是什么, 就要进行规定。所以Node并没有使用V8的默认事件循环,而是使用Libuv的事件循环,重写它的事件循环。由于V8的事件循环设计是插拔式的设计,所以很容易进行重新。
Environment* CreateEnvironment(Isolate* isolate, uv_loop_t* loop, Handle<Context> context, int argc, const char* const* argv, int exec_argc, const char* const* exec_argv) { HandleScope handle_scope(isolate); Context::Scope context_scope(context); Environment* env = Environment::New(context, loop); isolate->SetAutorunMicrotasks(false); uv_check_init(env->event_loop(), env->immediate_check_handle()); uv_unref(reinterpret_cast<uv_handle_t*>(env->immediate_check_handle())); uv_idle_init(env->event_loop(), env->immediate_idle_handle()); uv_prepare_init(env->event_loop(), env->idle_prepare_handle()); uv_check_init(env->event_loop(), env->idle_check_handle()); uv_unref(reinterpret_cast<uv_handle_t*>(env->idle_prepare_handle())); uv_unref(reinterpret_cast<uv_handle_t*>(env->idle_check_handle())); // Register handle cleanups env->RegisterHandleCleanup(reinterpret_cast<uv_handle_t*>(env->immediate_check_handle()), HandleCleanup, nullptr); env->RegisterHandleCleanup(reinterpret_cast<uv_handle_t*>(env->immediate_idle_handle()), HandleCleanup, nullptr); env->RegisterHandleCleanup(reinterpret_cast<uv_handle_t*>(env->idle_prepare_handle()), HandleCleanup, nullptr); env->RegisterHandleCleanup(reinterpret_cast<uv_handle_t*>(env->idle_check_handle()), HandleCleanup, nullptr); if (v8_is_profiling) { StartProfilerIdleNotifier(env); } Local<FunctionTemplate> process_template = FunctionTemplate::New(isolate); process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "process")); Local<Object> process_object = process_template->GetFunction()->NewInstance(); env->set_process_object(process_object); SetupProcessObject(env, argc, argv, exec_argc, exec_argv); LoadAsyncWrapperInfo(env); return env; }
CreateEnvironment 方法接受一个loop 作为参数,我们就可以把libuv的event loop 作为参数传递进来,V8 中有一个方法Environment::New,可以调用它来创建 V8 运行环境,它接受libuv 的event loop 作为参数,那么V8环境创建成功,它里面运行的事件循环,就是libuv的事件循环,成功的重写了V8 的事件循环。只有V8引擎中运行着事件循环,libuv中并不运行事件循环,它只是用代码实现了一个事件循环。