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

  • 相关阅读:
    ATM+购物车
    subprocess,re,logging模块
    json,pickle,collections,openpyxl模块
    time,datatime,random,os,sys,hashlib模块
    1.内置函数剩余部分 map reduce filter 2.函数递归 3.模块
    生成器,面向过程编程,三元表达式,列表生成式,生成器表达式,匿名函数,内置函数
    Ajax数据对接出问题了?ThingJS解决方法在这里
    测试
    简单低成本的物联网开发平台-ThingJS
    用ThingJS之CityBuilder快搭3D场景,可视化开发必备
  • 原文地址:https://www.cnblogs.com/SamWeb/p/14053356.html
Copyright © 2020-2023  润新知