Node.js 是如何工作的

  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中并不运行事件循环,它只是用代码实现了一个事件循环。

原文地址:https://www.cnblogs.com/SamWeb/p/14053356.html