web前端开发Node.js开发学习-- Node.js 的模块机制
小职 2021-09-28 来源 :编程杂技 阅读 952 评论 0

摘要:本篇主要介绍了web前端开发Node.js学习-- Node.js 的模块机制,通过具体的内容展现,希望对web前端Node.js开发的学习有一定的帮助。

本篇主要介绍了web前端开发Node.js学习-- Node.js 的模块机制,通过具体的内容展现,希望对web前端Node.js开发的学习有一定的帮助。

web前端开发Node.js开发学习-- Node.js 的模块机制


前言:模块机制是 Node.js 中非常重要的组成,模块机制使得我们可以以模块化的方式写代码,而不是全部代码都写到一个文件里。我们平时使用的比较多的通过 require 加载模块,但是我们可能不是很清楚 require 的实现原理,另外 Node.js 里存在多种模块类型,加载原理也不太一样,本文将会介绍 Node.js 模块机制以及实现原理。

 

1 模块机制的初始化和使用

1.1 注册 C++ 模块

 

在 Node.js 启动的时候,会通过 RegisterBuiltinModules 注册 C++ 模块。

 

void RegisterBuiltinModules() {   

 #define V(modname) _register_##modname();   

   NODE_BUILTIN_MODULES(V)   

 #undef V   

}

NODE_BUILTIN_MODULES是一个C语言宏,宏展开后如下(省略类似逻辑)

 

voidRegisterBuiltinModules() {   

    #define V(modname) _register_##modname();   

      V(tcp_wrap)    

      V(timers)   

      ...其它模块   

    #undef V   

}

再一步展开如下

 

void RegisterBuiltinModules() {   

  _register_tcp_wrap();   

  _register_timers();   

}

执行了一系列_register开头的函数,但是我们在Node.js源码里找不到这些函数,因为这些函数是在每个C++模块定义的文件里(.cc文件的最后一行)通过宏定义的。以tcp_wrap模块为例,看看它是怎么做的。文件tcp_wrap.cc的最后一句代码 NODE_MODULE_CONTEXT_AWARE_INTERNAL(tcp_wrap, node::TCPWrap::Initialize) 宏展开是

 

#define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc)  \   

    NODE_MODULE_CONTEXT_AWARE_CPP(modname,  

                                  regfunc,  

                                  nullptr,  

                                  NM_F_INTERNAL)

继续展开

 

define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \   

  static node::node_module _module = {              \   

      NODE_MODULE_VERSION,                        \   

      flags,                        \   

      nullptr,                        \   

      __FILE__,                        \   

      nullptr,                        \   

      (node::addon_context_register_func)(regfunc),  \   

      NODE_STRINGIFY(modname),                        \   

      priv,                        \   

      nullptr};                        \   

  void _register_tcp_wrap() { node_module_register(&_module); }

我们看到每个C++模块底层都定义了一个 _register 开头的函数,在 Node.js 启动时,就会把这些函数逐个执行一遍。我们继续看一下这些函数都做了什么,在这之前,我们要先了解一下Node.js中表示 C++ 模块的数据结构。

 

struct node_module {   

  int nm_version;   

  unsigned int nm_flags;   

  void* nm_dso_handle;   

  const char* nm_filename;   

  node::addon_register_func nm_register_func;   

  node::addon_context_register_func nm_context_register_func;   

  const char* nm_modname;   

  void* nm_priv;   

  struct node_module* nm_link;   

};

我们看到 _register 开头的函数调了 node_module_register,并传入一个 node_module 数据结构,所以我们看一下node_module_register 的实现

 

void node_module_register(void* m) {   

      struct node_module* mp = reinterpret_cast<struct node_module*>(m);   

      if (mp->nm_flags & NM_F_INTERNAL) {   

        mp->nm_link = modlist_internal;   

        modlist_internal = mp;   

      } else if (!node_is_initialized) {  

        mp->nm_flags = NM_F_LINKED;   

        mp->nm_link = modlist_linked;   

        modlist_linked = mp;   

      } else {   

        thread_local_modpending = mp;   

      }   

}

C++ 内置模块的 flag 是 NM_F_INTERNAL,所以会执行第一个if的逻辑,modlist_internal 类似一个头指针。if 里的逻辑就是头插法建立一个单链表。

 

1.2 初始化模块加载器

 

注册完 C++ 模块后,接着初始化模块加载器。

 

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()};

 

  // 执行 internal/bootstrap/loaders.js

  Local<Value> loader_exports;

  if (!ExecuteBootstrapper(

           this, "internal/bootstrap/loaders", &loaders_params, &loaders_args)

           .ToLocal(&loader_exports)) {

    return MaybeLocal<Value>();

  }

  // ...

}

ExecuteBootstrapper 会读取 internal/bootstrap/loaders.js 的内容,并且封装到一个函数中,这个函数如下

 

function (process, getLinkedBinding, getInternalBinding, primordials) {

    // internal/bootstrap/loaders.js 的内容

}

然后执行这个参数,并传入四个实参。我们看看 internal/bootstrap/loaders.js 执行后返回了什么。

 

const loaderExports = {

  // 加载 C++ 模块

  internalBinding,

  // 原生 JS 模块管理器

  NativeModule,

  // 原生 JS 加载器

  require: nativeModuleRequire

};

返回了两个模块加载器和一个模块管理器。接着 Node.js 把他们存起来,后续使用。

 

// 保存函数执行的返回结果

Local<Value> loader_exports;

if (!ExecuteBootstrapper(

         this, "internal/bootstrap/loaders", &loaders_params, &loaders_args)

         .ToLocal(&loader_exports)) {

  return MaybeLocal<Value>();

}

Local<Object> loader_exports_obj = loader_exports.As<Object>();

// 获取 C++ 模块加载器

Local<Value> internal_binding_loader = loader_exports_obj->Get(context(), internal_binding_string())

        .ToLocalChecked();

// 保存 C++ 模块加载器set_internal_binding_loader(internal_binding_loader.As<Function>());

// 获取原生 JS 加载器

Local<Value> require = loader_exports_obj->Get(context(), require_string()).ToLocalChecked();

// 保存原生 JS 加载器set_native_module_require(require.As<Function>());

1.3 执行用户 JS

 

Node.js 初始化完毕后最终会通过以下代码执行用户的代码。

 

StartExecution(env, "internal/main/run_main_module")

看看 StartExecution。

 

MaybeLocal<Value> StartExecution(Environment* env, const char* main_script_id) {

  EscapableHandleScope scope(env->isolate());

  CHECK_NOT_NULL(main_script_id);

 

  std::vector<Local<String>> parameters = {

      env->process_string(),

      // require 函数

      env->require_string(),

      env->internal_binding_string(),

      env->primordials_string(),

      FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")};

 

  std::vector<Local<Value>> arguments = {

      env->process_object(),

      // 原生 JS 和 C++ 模块加载器

      env->native_module_require(),

      env->internal_binding_loader(),

      env->primordials(),

      env->NewFunctionTemplate(MarkBootstrapComplete)

          ->GetFunction(env->context())

          .ToLocalChecked()};

 

  return scope.EscapeMaybe(

      ExecuteBootstrapper(env, main_script_id, ¶meters, &arguments));

}

传入了两个加载器,然后执行 run_main_module.js。核心代码如下

 

require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);

Module.runMain 的代码如下

 

function executeUserEntryPoint(main = process.argv[1]) {

  Module._load(main, null, true);

}

最终通过 _load 完成用户代码的加载和执行,下面我们具体分析各种加载器。

 

2 模块加载的实现

我们平时都是通过 require 加载模块,require 帮我们处理一切,其实 Node.js 中有很多种类型的模块,下面我们逐个介绍。

 

2.1 JSON 模块

 

Module._extensions['.json'] = function(module, filename) {

  const content = fs.readFileSync(filename, 'utf8');

 

  try {

    module.exports = JSONParse(stripBOM(content));

  } catch (err) {

    err.message = filename + ': ' + err.message;

    throw err;

  }

};

JSON 模块的实现很简单,读取文件的内容,解析一下就可以了。

 

2.2 用户 JS 模块

 web前端开发Node.js开发学习-- Node.js 的模块机制

 

 

我们看到为什么在写代码的时候可以直接使用 require 函数,不是因为 require 是全局变量,而是我们写的代码会被封装到一个函数里执行,require 和 module.exports 等变量都是函数的形参,在执行我们代码时, Node.js 会传入实参,所以我们就可以使用这些变量了。require 函数可以加载用户自定义的 JS,也可以加载原生 JS,比如net,不过 Node.js 会优先查找原生 JS。

 

2.3 原生 JS 模块

 web前端开发Node.js开发学习-- Node.js 的模块机制

 

 

原生 JS 模块和用户 JS 模块的加载原理是类似的,但是也有些不一样的地方,我们看到执行原生 JS 模块代码时,传入的实参和加载用户 JS 时是不一样的。首先 require 变量的值是一个原生 JS 模块加载器,所以原生 JS 模块里通过 require 只能加载 原生 JS 模块。另外还有另一个实参也需要关注,那就是 internalBinding,internalBinding 用于加载 C++ 模块,所以在原生 JS 里可以通过 internalBinding 加载 C++模块。

 

2.4 C++ 模块

 

 web前端开发Node.js开发学习-- Node.js 的模块机制

 

2.5 Addon 模块

web前端开发Node.js开发学习-- Node.js 的模块机制


我是小职,记得找我

✅ 解锁高薪工作

✅ 免费获取基础课程·答疑解惑·职业测评

web前端开发Node.js开发学习-- Node.js 的模块机制

本文由 @小职 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程