cover-nodejs-logo.png

node.js简述

node.js是嵌入式v8应用。node.js为v8提供运行时,v8为node提供javaScript的编译执行。另外node.js实现了诸如文件系统、模块、包、操作系统 API、网络通信等功能模块。为此我们能在node中可以读取文件,访问socket创建网络端口等。
v2-9a25de320e0a1e57c0c95d7b9d943531_r.jpg
与node不同的是,chromium也是v8的嵌入式应用。chromium为v8提供了运行时。但chromium并没有为v8提供了文件系统等功能模块。这由于网页是一种动态加载的文件,对于操作系统来说,他是一个不可信的文件类型,因此chromium并没有为v8提供直接访问本地文件的能力。但chromium为v8提供了操作网页功能,ajax网络访问。因此可以在chromium中v8中通过javaScript去创建图形界面,通过canvas去画图。通过webgl去执行opengl命令等。
node.js的启动流程分析 - 图4
node.js依靠v8提供了javaScript解析执行。依靠libuv提供了事件循环,跨平台文件网络io等能力。整个node.js应用都围绕着着两个模块做展开。再提供一些commonjs运行时的内建javaScript模块等和运行时内置对象共同组成了node。依靠本人的经验。node.js的实现并不优雅,模块拆分也很乱甚至有点糟糕的地步。整体实现难度中等。后面有打算写一个教程,关于如何从零开始编写一个node。

node源码启动流程分析

流程有点长,请仔细阅读。下面的内容会围绕着这个展开。本下面都是在windows系统下讨论。node.js的启动流程(wps真香)
node.js启动流程.png

  1. node_main.ccwmain()为windows系统下的node入口函数。在入口函数中只要是做了windows操作系统的判断。并把参数由utf-16编码转换成utf-8的格式。最终调用node::Start()函数执行下面的流程。 ```cpp

int wmain(int argc, wchar_t* wargv[]) {

char buf[SKIP_CHECK_SIZE + 1]; // 操作系统的判断
if (!IsWindows8Point1OrGreater() && !(IsWindowsServer() && IsWindows8OrGreater()) && (GetEnvironmentVariableA(SKIP_CHECK_VAR, buf, sizeof(buf)) != SKIP_CHECK_SIZE || strncmp(buf, SKIP_CHECK_VALUE, SKIP_CHECK_SIZE + 1) != 0)) { fprintf(stderr, “Node.js is only supported on Windows 8.1, Windows “ “Server 2012 R2, or higher.\n” “Setting the “ SKIP_CHECK_VAR “ environment variable “ “to 1 skips this\ncheck, but Node.js might not execute “ “correctly. Any issues encountered on\nunsupported “ “platforms will not be fixed.”); exit(ERROR_EXE_MACHINE_TYPE_MISMATCH); } // c语言的字符串后面需要加\0结尾 char* argv = new char[argc + 1];

// 把参数的编码有utf-16装换成utf-8 for (int i = 0; i < argc; i++) {

  1. DWORD size = WideCharToMultiByte(CP_UTF8,
  2. 0,
  3. wargv[i],
  4. -1,
  5. nullptr,
  6. 0,
  7. nullptr,
  8. nullptr);
  9. if (size == 0) {
  10. fprintf(stderr, "Could not convert arguments to utf8.");
  11. exit(1);
  12. }
  13. argv[i] = new char[size];
  14. DWORD result = WideCharToMultiByte(CP_UTF8,
  15. 0,
  16. wargv[i],
  17. -1,
  18. argv[i],
  19. size,
  20. nullptr,
  21. nullptr);
  22. if (result == 0) {
  23. fprintf(stderr, "Could not convert arguments to utf8.");
  24. exit(1);
  25. }

} argv[argc] = nullptr; // 执行主流程 return node::Start(argc, argv); }


2. `node.cc ` 整个node的流程都在文件的`Start()` 函数中做处理。下面流程也是在这个函数的执行展开。
```cpp
int Start(int argc, char** argv) {
  /* InitializeOncePerProcess 函数代表着只下只执行一次初始化,该函数只要做了一下的初始化工作
  *  1:平台的初始化,只要是出入输出
  *  2:初始化node的逻辑,包裹内建原生模块注册,内建javaScript模块的缓存数据的初始化,环境变量,icu更国际化等
  *  3: v8的初始化
  */
  InitializationResult result = InitializeOncePerProcess(argc, argv);
  if (result.early_return) {
    return result.exit_code;
  }
  {
    // 创建v8隔离实例所需要的参数
    Isolate::CreateParams params;
    const std::vector<size_t>* indexes = nullptr;
    const EnvSerializeInfo* env_info = nullptr;
    bool force_no_snapshot =  per_process::cli_options->per_isolate->no_node_snapshot;
    // 是否使用快照的方式启动, 该快照由node自定义的快照文件。
    if (!force_no_snapshot) {
      // 获取快照数据
      v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob();
      if (blob != nullptr) {
        // 设置快照数据
        params.snapshot_blob = blob;
        indexes = NodeMainInstance::GetIsolateDataIndexes();
        env_info = NodeMainInstance::GetEnvSerializeInfo();
      }
    }
    // libuv事件循环配置
    uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);

    // 创建node的主实例。整个node的运行都是围绕着NodeMainInstance实例开展
    NodeMainInstance main_instance(&params,
                                   uv_default_loop(),
                                   per_process::v8_platform.Platform(),
                                   result.args,
                                   result.exec_args,
                                   indexes);
    // 执行主流程  
    result.exit_code = main_instance.Run(env_info);
  }

  TearDownOncePerProcess();
  return result.exit_code;
}
  1. InitializeOncePerProcess()初始化函数,在该函数执行的初始化在node进程内只会执行一次。
  2. InitializeNodeWithArgs()初始化node。包括执行注册内建原生模块函数。设置命令行参数例如设置v8的一些flags(v8::V8::SetFlagsFromString(“—abort-on-uncaught-exception”)),处理环境变量(例如我们在环境变量中配置node的的libuv启用的线程数量默认是4,通过配置NODE_OPTIONS 的v8_thread_pool_size=4修改libuv默认启动线程数量。参数列表)。初始化内建javaScript模块的缓存cacheData。

    // 代码有省略,只要是v8的icu(国际化)初始化
    **
    * argv 命令行参数
    * errors 生成的错误列表
    */
    int InitializeNodeWithArgs(std::vector<std::string>* argv,
                            std::vector<std::string>* exec_argv,
                            std::vector<std::string>* errors) {
    
    // 注册c++内建模块
    binding::RegisterBuiltinModules();
    
    // 初始化内建javaScript模块的CacheData
    NativeModuleEnv::InitializeCodeCache();
    
    // 标记node 初始化完成
    //  node_is_initialized 为一个全局变量
    node_is_initialized = true;
    return 0;
    }
    
  3. v8的初始化,v8平台的初始化,包括线程池轨迹控制器等的初始化等。

    // 内容做了删减
    InitializationResult InitializeOncePerProcess(int argc, char** argv) {
    
    PlatformInit();
    
    InitializationResult result;
    result.args = std::vector<std::string>(argv, argv + argc);
    std::vector<std::string> errors;
    
    // node的初始化, 上面做了讲解
    {
     result.exit_code = InitializeNodeWithArgs(&(result.args), &(result.exec_args), &errors);
     for (const std::string& error : errors)
       fprintf(stderr, "%s: %s\n", result.args.at(0).c_str(), error.c_str());
     if (result.exit_code != 0) {
       result.early_return = true;
       return result;
     }
    }
    
    // 初始化v8平台
    // 初始化v8的线程池的大小,v8的轨迹控制器
    per_process::v8_platform.Initialize(
       static_cast<int>(per_process::cli_options->v8_thread_pool_size));
    V8::Initialize();
    // 标记v8已经进行了初始化
    per_process::v8_initialized = true;
    return result;
    }
    
  4. 创建NodeMainInstance, 在NodeMainInstance的实例创建v8隔离实例(Isolate), 并为v8隔离实例对象设置相关回调。

    NodeMainInstance::NodeMainInstance(
     Isolate::CreateParams* params,
     uv_loop_t* event_loop,
     MultiIsolatePlatform* platform,
     const std::vector<std::string>& args,
     const std::vector<std::string>& exec_args,
     const std::vector<size_t>* per_isolate_data_indexes)
     : args_(args),
       exec_args_(exec_args),
       array_buffer_allocator_(ArrayBufferAllocator::Create()),
       isolate_(nullptr),
       platform_(platform),
       isolate_data_(nullptr),
       owns_isolate_(true) {
    
    isolate_ = Isolate::Allocate();
    Isolate::Initialize(isolate_, *params);
    IsolateSettings s;
    // 设置隔离实例的回调函数
    SetIsolateMiscHandlers(isolate_, s);
    }
    
  5. NodeMainInstance::Run()执行整个node应用的核心。创建Environment。Environment是node中一个重要的类,类中包含了属性包括隔离实例,执行上下文等属性,还有node中用于加载内建模块和原生模块的函数。在创建该对象后,在往后的操作调用函数都使用了Environment对象作为参数传递给每个调用的函数。更准确来说。Environment对象是node中的上下文对象。

    int NodeMainInstance::Run(const EnvSerializeInfo* env_info) {
    Locker locker(isolate_);
    Isolate::Scope isolate_scope(isolate_);
    HandleScope handle_scope(isolate_);
    
    int exit_code = 0;
    // 创建Environment 对象,包括创建v8执行上下文。并为执行上下的全局代理对象添加全局属性
    DeleteFnPtr<Environment, FreeEnvironment> env = CreateMainEnvironment(&exit_code, env_info);
    {
    Context::Scope context_scope(env->context());
    if (exit_code == 0) {
      // 执初始化libuv,执行主模块
      LoadEnvironment(env.get(), StartExecutionCallback{});
      // 执行事件循环
      exit_code = SpinEventLoop(env.get()).FromMaybe(1);
    }
    return exit_code;
    }
    

    通过NodeMainInstance::CreateMainEnvironment()创建Environment对象。 ```cpp DeleteFnPtr NodeMainInstance::CreateMainEnvironment(int exit_code,const EnvSerializeInfo envinfo) { *exit_code = 0; HandleScope handle_scope(isolate); // 期待隔离实例堆分析 if (isolatedata->options()->trackheap_objects) { isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); }

Local context; DeleteFnPtr env; // 创建v8执行上下文 context = NewContext(isolate); Context::Scope context_scope(context); env.reset(new Environment(isolate_data.get(), context, args, exec_args, nullptr, EnvironmentFlags::kDefaultFlags, {})); if (env->RunBootstrapping().IsEmpty()) { return nullptr; } return env; }


8. 创建v8执行上下文。
```cpp
Local<Context> NewContext(Isolate* isolate,
                          Local<ObjectTemplate> object_template) {
  auto context = Context::New(isolate, nullptr, object_template);
  if (context.IsEmpty()) return context;
  // 初始化执行上下文,
  if (!InitializeContext(context)) {
    return Local<Context>();
  }
  return context;
}

执行上下文的初始化。


bool InitializeContext(Local<Context> context) {
  // 通过快照去初始化v8执行上下文
  if (!InitializeContextForSnapshot(context)) {
    return false;
  }
  // 初始化执行上下运行时,只要是移除全局对象上一个导致bug的属性。
  InitializeContextRuntime(context);
  return true;
}
void InitializeContextRuntime(Local<Context> context) {
  Isolate* isolate = context->GetIsolate();
  HandleScope handle_scope(isolate);

  // 删除 `Intl.v8BreakIterator`
  Local<String> intl_string = FIXED_ONE_BYTE_STRING(isolate, "Intl");
  Local<String> break_iter_string = FIXED_ONE_BYTE_STRING(isolate, "v8BreakIterator");
  Local<Value> intl_v;
  if (context->Global()->Get(context, intl_string).ToLocal(&intl_v) && intl_v->IsObject()) {
    Local<Object> intl = intl_v.As<Object>();
    intl->Delete(context, break_iter_string).Check();
  }

  // 删除 `Atomics.wake`
  Local<String> atomics_string = FIXED_ONE_BYTE_STRING(isolate, "Atomics");
  Local<String> wake_string = FIXED_ONE_BYTE_STRING(isolate, "wake");
  Local<Value> atomics_v;
  if (context->Global()->Get(context, atomics_string).ToLocal(&atomics_v) &&
      atomics_v->IsObject()) {
    Local<Object> atomics = atomics_v.As<Object>();
    atomics->Delete(context, wake_string).Check();
  }

  //  __proto__
  Local<String> object_string = FIXED_ONE_BYTE_STRING(isolate, "Object");
  Local<String> prototype_string = FIXED_ONE_BYTE_STRING(isolate, "prototype");
  Local<Object> prototype = context->Global()
                                ->Get(context, object_string)
                                .ToLocalChecked()
                                .As<Object>()
                                ->Get(context, prototype_string)
                                .ToLocalChecked()
                                .As<Object>();
  Local<String> proto_string = FIXED_ONE_BYTE_STRING(isolate, "__proto__");
  if (per_process::cli_options->disable_proto == "delete") {
    prototype->Delete(context, proto_string).ToChecked();
  } else if (per_process::cli_options->disable_proto == "throw") {
    Local<Value> thrower =
        Function::New(context, ProtoThrower).ToLocalChecked();
    PropertyDescriptor descriptor(thrower, thrower);
    descriptor.set_enumerable(false);
    descriptor.set_configurable(true);
    prototype->DefineProperty(context, proto_string, descriptor).ToChecked();
  } else if (per_process::cli_options->disable_proto != "") {
    // Validated in ProcessGlobalArgs
    FatalError("InitializeContextRuntime()", "invalid --disable-proto mode");
  }
}
  1. 初始化v8执行上下文。在Environment::InitializeMainContext()函数上执行v8执行上下文的初始化。创建进程对象(process),创建primordials对象,primordials对象保存着标准javaScript全局对象原型上的函数的应用集合。 ```cpp void Environment::InitializeMainContext(Local context,
                                     const EnvSerializeInfo* env_info) {
    
    context.Reset(context->GetIsolate(), context); // 创建属性 CreateProperties(); } void Environment::CreateProperties() { HandleScope handle_scope(isolate); Local ctx = context();

// 获取每个上下文需要的一些binding对象。 Local per_context_bindings = GetPerContextExports(ctx).ToLocalChecked();

// primordials 中文为原始值,它是内置对象原型上的对象或者方法的引用。 // 通过执行内建javaScript文件 internal\per_context\primordials.js对标准javaScript内置全局对象进行原型上的方法或者函数进行劫持 // 例如:函数上的原型方法Function.prototype.call, primordials = { FunctionProperPrototypeCall: Function.prototype.call } // primordials 对象保存着原始的标准javaScript全局对象上的原型函数上的引用。这样。当用户模块改变了 Function.prototype.call = null 后,内建模块也不会遭受影响 Local primordials = per_context_bindings->Get(ctx, primordials_string()).ToLocalChecked(); set_primordials(primordials.As());

Local prototype_string = FIXED_ONE_BYTE_STRING(isolate(), “prototype”);

define V(EnvPropertyName, PrimordialsPropertyName) \

{ \ Local ctor = \ primordials.As() \ ->Get(ctx, \ FIXEDONE_BYTE_STRING(isolate(), PrimordialsPropertyName)) \ .ToLocalChecked(); \ CHECK(ctor->IsObject()); \ Local prototype = \ ctor.As()->Get(ctx, prototype_string).ToLocalChecked(); \ CHECK(prototype->IsObject()); \ set##EnvPropertyName(prototype.As()); \ }
V(primordials_safe_map_prototype_object, “SafeMap”); V(primordials_safe_set_prototype_object, “SafeSet”); V(primordials_safe_weak_map_prototype_object, “SafeWeakMap”); V(primordials_safe_weak_set_prototype_object, “SafeWeakSet”);

undef V

// 创建进程对象 Local process_object = node::CreateProcessObject(this).FromMaybe(Local()); set_process_object(process_object); }


9. 执行`Environment::RunBootstrapping()`运行一些内建javaScript模块,生成一些node运行时所需的函数或者对象。包括获取内建模块的InternalBinding函数,获取内建javaScript模块的require函数等。<br />
```cpp
MaybeLocal<Value> Environment::RunBootstrapping() {
  EscapableHandleScope scope(isolate_);

  CHECK(!has_run_bootstrapping_code());

  // 执行internal\bootstrap\loaders.js
  if (BootstrapInternalLoaders().IsEmpty()) {
    return MaybeLocal<Value>();
  }

  Local<Value> result;
  // 执行internal\bootstrap\node.js
  if (!BootstrapNode().ToLocal(&result)) {
    return MaybeLocal<Value>();
  }
  return scope.Escape(result);
}
  1. Environment::BootstrapInternalLoaders加载执行 internal\bootstrap\loaders.js,把binding::GetLinkedBinding(获取链接模块),binding::GetInternalBinding(获取内建 c++模块), Primordials对象,process对象,作为参数传递给 laoder.js。进程在loader.js做了一层javaScript缓存包装后。再导出到c++层,InternalBinding(获取内建模块),require(获取内建javaScript模块)。

    MaybeLocal<Value> Environment::BootstrapInternalLoaders() {
    EscapableHandleScope scope(isolate_);
    
    // 创建形参列表
    std::vector<Local<String>> loaders_params = {
       process_string(),
       FIXED_ONE_BYTE_STRING(isolate_, "getLinkedBinding"),
       FIXED_ONE_BYTE_STRING(isolate_, "getInternalBinding"),
       primordials_string()};
    
    // 构建实参列表   
    std::vector<Local<Value>> loaders_args = {
       process_object(),
       NewFunctionTemplate(binding::GetLinkedBinding)
           ->GetFunction(context())
           .ToLocalChecked(),
       NewFunctionTemplate(binding::GetInternalBinding)
           ->GetFunction(context())
           .ToLocalChecked(),
       primordials()};
    Local<Value> loader_exports;
    
    // 加载loaders.js文件 把文件包装成js函数
    // 参数为getLinkedBinding,getInternalBinding
    // loader.js里面的js代码可以获取这两个参数进行js与c++桥接调用。
    // loader.js 初始化模块加载 包括加载 .node的c++扩展   require(xxx.node) ->  process.binding(),node的原生模块 require(fs) -> nativeModuleRequire()
    if (!ExecuteBootstrapper(
            this, "internal/bootstrap/loaders", &loaders_params, &loaders_args)
            .ToLocal(&loader_exports)) {
     return MaybeLocal<Value>();
    }
    
    /*
    *  loader_exports 为执行的javaScript结果
    *  在loader.js中,返回来
    *  const loaderExports = {
    *      // internalBinding 使用了javaScript对象对getInternalBinding 进行缓存封装
    *      internalBinding: internalBinding,
    *      // 一个javaScript class, 原生模块的结构,同时包装了一个运行需要的函数
    *      NativeModule: NativeModule,
    *      // 获取原生模块的函数
    *      require : nativeModuleRequire
    *  };
    */
    
    Local<Object> loader_exports_obj = loader_exports.As<Object>();
    
    // 获取 internalBinding函数, internalBinding函数用于获取内建原生模块
    Local<Value> internal_binding_loader =
       loader_exports_obj->Get(context(), internal_binding_string())
           .ToLocalChecked();
    
    // 保存函数到Environment对象上
    set_internal_binding_loader(internal_binding_loader.As<Function>());
    
    // 获取 require 函数
    Local<Value> require =
       loader_exports_obj->Get(context(), require_string()).ToLocalChecked();
    
    // 保存函数到Environment对象上
    set_native_module_require(require.As<Function>());
    return scope.Escape(loader_exports);
    }
    
  2. Environment::BootstrapNode() 加载执行 internal\bootstrap\node.js,创建全局对象属性Buffer,setTimeOutsetInterval函数,process对象等。为进程对象添加属性。例如process.dlopen()等

    MaybeLocal<Value> Environment::BootstrapNode() {
    EscapableHandleScope scope(isolate_);
    
    Local<Object> global = context()->Global();
    
    // 把全局代理对象设置globa属性对自己的引用
    global->Set(context(), FIXED_ONE_BYTE_STRING(isolate_, "global"), global)
      .Check();
    
    // 创建形参,参数名为
    // process: 进程对象
    // require:获取内建javaScript模块
    // internalBinding:获取内建模块
    // primordials:原始类型对象
    std::vector<Local<String>> node_params = {
      process_string(),
      require_string(),
      internal_binding_string(),
      primordials_string()};
    // 构建实参
    std::vector<Local<Value>> node_args = {
      process_object(),
      native_module_require(),
      internal_binding_loader(),
      primordials()};
    
    //执行internal/bootstrap/node.js函数
    MaybeLocal<Value> result = ExecuteBootstrapper(
      this, "internal/bootstrap/node", &node_params, &node_args);
    
    if (result.IsEmpty()) {
    return scope.EscapeMaybe(result);
    }
    // 根据当前的线程类型执行不同的脚本。脚本只要是为了初始化不同线程的输入输出流的初始化
    auto thread_switch_id =  is_main_thread()
                          ? "internal/bootstrap/switches/is_main_thread"
                       : "internal/bootstrap/switches/is_not_main_thread";
    result = ExecuteBootstrapper(this, thread_switch_id, &node_params, &node_args);
    
    if (result.IsEmpty()) {
    return scope.EscapeMaybe(result);
    }
    
    auto process_state_switch_id =
      owns_process_state()
          ? "internal/bootstrap/switches/does_own_process_state"
          : "internal/bootstrap/switches/does_not_own_process_state";
    result = ExecuteBootstrapper(
      this, process_state_switch_id, &node_params, &node_args);
    
    if (result.IsEmpty()) {
    return scope.EscapeMaybe(result);
    }
    
    Local<String> env_string = FIXED_ONE_BYTE_STRING(isolate_, "env");
    Local<Object> env_var_proxy;
    
    // 创建 process.env 代理
    // 当我们通过进程对象 process.env访问某个属性的时候。实际上的逻辑会在c++层处理。
    if (!CreateEnvVarProxy(context(), isolate_).ToLocal(&env_var_proxy) ||
      process_object()->Set(context(), env_string, env_var_proxy).IsNothing()) {
    return MaybeLocal<Value>();
    }
    
    return scope.EscapeMaybe(result);
    }
    
  3. LoadEnvironment()注册一些libuv在时间循环不同时期的回调函数和注册隔离实例关于诊断的回调函数。最后执行主模块。 ```cpp MaybeLocal LoadEnvironment( Environment* env, StartExecutionCallback cb) { // 为libuv注册回调你函数。 env->InitializeLibuv(); // 注册隔离实例诊断函数 env->InitializeDiagnostics(); // 执行主模块 return StartExecution(env, cb); }


12.  `StartExecution()` 加载run_main_module.js,这是用于运行node的主模块的入口逻辑,主模块加载前会运行另外的模块pre_execution.js。该模块只要在为了在执行主模块前执行一些初始化。例如定义了Module的javaScript对象,并在该对象上挂载cache缓存等属性,还有在家了我们在node.js模块中使用了require函数。另外在在这个函数中再设置了一些属性。最后会执行主模块的代码。
```javascript
// run_main_module.js
const {
  prepareMainThreadExecution
} = require('internal/bootstrap/pre_execution');
prepareMainThreadExecution(true);
markBootstrapComplete();
// 执行主模块,在prepareMainThreadExecution中为Module类添加了静态方法runMain。runmain是用于执行javaScript入口文件。
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);
function executeUserEntryPoint(main = process.argv[1]) {
  const resolvedMain = resolveMainPath(main);
  // 通过条件判断是否使用esmodule的方式启动node 主模块。
  const useESMLoader = shouldUseESMLoader(resolvedMain);
  if (useESMLoader) {
    // 使用了esmodule的方式去加载主模块
    runMainESM(resolvedMain || main);
  } else {
    // 使用commonjs去加载主模块
    Module._load(main, null, true);
  }
}

function shouldUseESMLoader(mainPath) {
  // 查看是否传入--experimental-loader 命令
  const userLoader = getOptionValue('--experimental-loader');
  if (userLoader)
    return true;
  const esModuleSpecifierResolution =
    getOptionValue('--experimental-specifier-resolution');
  if (esModuleSpecifierResolution === 'node')
    return true;

  // 判断是否使用.mjs结尾的文件。
  if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs'))
    return true;
  // 是否使用了.cjs文件
  if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs'))
    return false;
  // 判断工程中的package.json 文件中是否有字段type 并却字段的值为 'module'
  const pkg = readPackageScope(mainPath);
  return pkg && pkg.data.type === 'module';
}

在执行主模块入口函数的时候,会先做出判断处理。是否使用esmodule的方式去运行主模块。流程图
node判断是否使用semodule方式启动主模块.png

  1. SpinEventLoop()启动libuv事件循环。 ```cpp

Maybe SpinEventLoop(Environment env) { CHECK_NOT_NULL(env); MultiIsolatePlatform platform = GetMultiIsolatePlatform(env); CHECK_NOT_NULL(platform);

HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); SealHandleScope seal(env->isolate());

if (env->is_stopping()) return Nothing();

env->set_trace_sync_io(env->options()->trace_sync_io); { bool more; env->performance_state()->Mark( node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START); do { if (env->is_stopping()) break; uv_run(env->event_loop(), UV_RUN_DEFAULT); if (env->is_stopping()) break;

  platform->DrainTasks(env->isolate());
  // 判断是否还有事件没有处理完毕
  more = uv_loop_alive(env->event_loop());
  // 如果有继续执行  
  if (more && !env->is_stopping()) continue;

  if (EmitProcessBeforeExit(env).IsNothing())
    break;
  more = uv_loop_alive(env->event_loop());
} while (more == true && !env->is_stopping());

env->performance_state()->Mark(
    node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);

} if (env->is_stopping()) return Nothing();

env->set_trace_sync_io(false); env->VerifyNoStrongBaseObjects(); return EmitProcessExit(env); }


14. 如果当前没有事件处理。退出事件循环,并做关闭v8等做善后工作。
14. 退出程序。

可以看到,整个node.js的加载,首先是做了初始化工作。包括v8的初始化,libuv的初始化。还有node运行时需要的一些内置的函数和对象进行了初始化。然后执行入口模块。最后进入事件循环。在事件循环中,如果没有需要处理的事件,就会退出循环,最后整个程序执行完成。
<a name="ZnyBw"></a>
# node.js模块系统
node.js的模块化系统由两部分组成,一部分是居于commonjs规范,以函数作用域为基础的模块化系统,但它没有完全实现commonjs规范。在node.js编写模块的时候不需要像commonjs规范那样需要编写define函数。node.js会自动把文件内容包装成一个函数。然后传递require, exports, module, ,__dirname作为参数传递到函数里面执行。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2747064/1622612308430-e360adf3-16eb-4fc8-8194-afe45b1d8d98.png#align=left&display=inline&height=460&margin=%5Bobject%20Object%5D&name=image.png&originHeight=730&originWidth=766&size=43770&status=done&style=none&width=483)<br />另一种是标准的模块化esmodule,包括静态模块和动态模块。概括起来,在node.js的模块系统中有7种类型模块,包括5中commonjs的模块和2种标准模块。
> 1. 内建原生模块
> ```javascript
// 通过process.binding获取的是不是内建javaScript模块,是内建的原生模块,在此获取的是c++部分的fs。
process.binding("fs");
  1. 链接原生模块
    // 链接模块
    process._linkedBinding();
    
  2. 内建 javaScript模块,也成为native模块
    // 在此获取的是内建的javaScript模块
    require("fs");
    
  3. 用户模块,包括用户编写的javaScipr文件和json文件。
    require('./module')
    
  4. 原生c++扩展。使用了c++开发的一种动态扩展。可以使用v8的api或者node-api。
    require("module.node")
    
  5. 标准模块
    import { fun } from "./module"
    
  6. 标准动态模块
    const { fun } = await import("./module")
    

node在使用esmodule为主模块的启动整个node应用的时候,需要兼容以前的commonjs的模块。为此node使用了一种hack的方式去做了兼容。后面章节做出解析。