zoukankan      html  css  js  c++  java
  • 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中并不运行事件循环,它只是用代码实现了一个事件循环。

  • 相关阅读:
    ThinkPHP 3.2.2 实现持久登录 ( 记住我 )
    Java实现 LeetCode 20 有效的括号
    Java实现 LeetCode 20 有效的括号
    Java实现 LeetCode 19删除链表的倒数第N个节点
    Java实现 LeetCode 19删除链表的倒数第N个节点
    Java实现 LeetCode 19删除链表的倒数第N个节点
    Java实现 LeetCode 18 四数之和
    Java实现 LeetCode 18 四数之和
    Java实现 LeetCode 18 四数之和
    Java实现 LeetCode 17 电话号码的字母组合
  • 原文地址:https://www.cnblogs.com/SamWeb/p/14053356.html
Copyright © 2011-2022 走看看