美文网首页
模块机制

模块机制

作者: jluemmmm | 来源:发表于2020-09-16 23:08 被阅读0次

nodejs模块采用Commonjs规范,弥补Javascript开发大型应用没有标准的缺陷,类似java中的类,python中的import,nodejs通过module.exportsrequire来导出和引入一个模块。nodejs技术栈

模块分类

1. 系统模块

  • C/C++模块,也叫built-in内建模块,用于native模块调用,源码存放于node/src目录
  • native模块,开发中使用的httpbufferfs等,底层调用内建模块,源码存放于node/lib目录

2. 三方模块

非nodejs自带的模块称为三方模块,包括路径形式的文件模块(以.../)和自定义模块(如express、koa框架等)

模块加载机制

在Node中引入模块,需要经历三个步骤, 路径分析文件定位编译执行

  1. 缓存检查:node对引入过的模块(包括系统模块和文件模块)会进行缓存,存编译和执行后的对象。require方法对相同模块的二次加载都采用缓存优先的方式,核心模块的缓存检查先于文件模块的缓存检查。
  1. 加载系统模块:系统模块在node源码编译的过程中,编译成为二进制文件,node进程启动时,部分核心模块就被直接加载进内存中,这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略,并在路径分析中优先判断。 (native模块如何进行路径分析?)
  1. 路径形式的文件模块:require方法将相对路径转换为绝对路径,将编译执行的结果放入缓存中。如果文件没有加上扩展名,会依次按照.js.json.node进行补足尝试。在尝试的过程中,需要调用fs模块同步阻塞式地判断文件是否存在。在分析标识符的过程中,**require通过分析出文件扩展名之后,可能没有查找到对应的文件,得到一个目录,在引入自定义模块个逐个模块进行路径查找时经常出现,node会将目录当作一个包来处理。
  • 首先,node在当前目录下查找package.json,通过json.parse解析出包描述对象,从中取出main属性执行的文件名进行定位,如果文件缺少扩展名,将会进入扩展名分析的步骤。
  • 如果main属性指定的文件名错误,或者没有package.json文件,node会将index当作默认文件名,依次查找index.jsindex.jsonindex.node

4. 包名形式的文件模块: 模块路径是Node在定位文件模块的具体文件时的策略,表现为一个路径组成的数组。模块路径的生成规则为:

  • 当前文件目录下的node_modules目录
  • 父目录下的node_modules目录
  • 父目录的父目录下的node_modules目录
  • 沿路径逐级向上递归,直到根目录下的node_modules目录
    在个目录下查找文件及文件夹,如果模块路径数组被遍历完毕,依然没有查找到目标文件,则抛出查找失败的异常。

模块编译

结合源码分析 Node.js 模块加载与运行原理

定位到具体的文件后,node会新建一个模块对象,根据路径载入并编译。对于不同文件的扩展名,载入方法有所不同。

  • .js文件,通过fs模块同步读取文件后编译执行
  • .node文件,用C/C++编写的扩展文件,通过dlopen()方法加载最后编译的文件
  • .json文件 通过fs模块同步读取文件后,用JSON.parse解析返回结果

js模块的编译

在编译过程中,node对获取的js文件内容进行包装,这样每个模块文件之间进行了作用域隔离,包装之后的代码会通过vm原生模块runInThisContext方法执行(具有明确上下文,不污染全局),返回一个具体的function对象,将当前模块对象的exports属性,require方法,module以及在文件定位中得到的完整文件路径和文件目录作为参数传递给这个function执行。

(function(exports, require, module, __filename, __dirname) {
  var math = require('math')
  exports.area = function(radius) {
    return Math.PI * radius * radius
  }
})

在执行之后,模块的exports属性被返回,exports属性上的任何方法和属性都可以被外部调用,模块中的其余变量或属性则不可被直接调用。不能直接赋值exportsexports对象通过形参方式传入,直接赋值形参会改变形参的引用,不会改变作用域外的值。

其中,javascript核心模块编译过程为

  1. 转存为C/C++代码

Node采用了V8附带的js2c.py工具,将所有内置的js代码(src/node.jslib/*.js)转换成C++里的数组,生成node_native.h头文件。在这个过程中,js代码以字符串的形式存储在node命名空间中,不可直接执行。在启动node进程时,js代码直接加载进内存中。在加载过程中,js核心模块经历标识符分析后直接定位到内存中,比普通文件模块从磁盘中一处一处查找要快得多。

  1. 编译Javascript 核心模块

在编译过程中,node对获取的js文件内容进行包装,模块文件进行了作用域隔离,包装之后的代码会通过vm原生模块runInThisContext方法执行(具有明确上下文,不污染全局),返回一个具体的function对象,将当前模块对象的exports属性,require方法,module__dirnamefilename作为参数传递给这个function执行。js核心模块中,源文件通过process.binding('natives')取出,编译成功的模块缓存到NativeModule._cache对象上,文件模块则缓存到Module._cache对象上。

function NativeModule(id) {
  this.filename = id + '.js'
  this.id = id
  this.exports = {}
  this.loaded = false
}
NativeModule._source = process.binding('natives')
NativeModule._cache = {}

C/C++模块的编译

node调用process.dlopen()方法进行加载和执行,node的dlopen方法在windows*.nix平台下有不同的实现,通过libuv(跨平台的异步I/O库)层进行封装,使用 uv_dlopen 方法打开动态链接库,然后对 C/C++ 模块进行加载。在 *nix 平台下,实际上调用的是 dlfcn.h 中定义的 dlopen()方法,而在 Windows下,则为LoadLibraryExW()方法,在两个平台下,他们加载的分别是 .so.dll 文件,而 Node.js 中,这些文件统一被命名了 .node 后缀,屏蔽了平台的差异。

require()引入.node文件的过程

C/C++核心模块的编译过程

核心模块中,有些模块由C/C++编写,有些模块由C/C++完成核心部分,其他部分由javascript实现或向外导出。由纯C/C++编写的部分称为内建模块,不直接被用户调用。

strcuct node_module_struct {
  int version
  void *dso_handle
  const char *filename
  void (*register_func)(v8::Handle<v8::Object> target)
  const char *modname
}

1. 每个内建模块在定义之后,都通过NODE_MODULE宏将模块定义到node命名空间中,模块的具体初始化方法挂载为结构的register_func成员。

2. node_extensions.h将这些散列的内建模块同一放入node_module_list数组中,node通过get_builtin_module()方法从node_module_list数组中取出这些模块。

3. node在启动过程中,会生成一个全局变量process,并提供binding方法(实现在src/node.cc中)加载内建模块,先创建exports空对象,然后调用get_builtin_module()方法取出内建模块对象,通过执行register_func()填充exports对象,最后将exports对象按模块名缓存,并返回给调用方法完成导出。

核心模块的os的引入流程

require('os')
 |
\|/
NativeModule.require('os')
 |
\|/
process.binding('os')
 |
\|/
get_builtin_module('node_os')
 |
\|/
NODE_MODULE(node_os, reg_func)

commonjs规范

commonjs规范提出是为了弥补当前js没有标准的缺陷,期望用commonjs API写出的应用程序可以具备跨宿主环境执行的能力,规范涵盖了模块、二进制、buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、web服务器网关接口,包管理。

  • 模块引用
var math = require('math')
  • 模块定义
    模块中存在一个module对象,代表模块自身,exportsmodule的属性,exports对象用于导出当前模块的方法或变量。

  • 模块标识
    模块标识是传递给require方法的参数,需要时以小驼峰命名的字符串,或者路径。

相关文章

  • Vue.js 学习之路(二)

    npm install npm install 模块安装机制简介 关于 npm install 模块安装机制的简介...

  • Node.js 模块机制

    Node.js 模块机制 Node.js 模块机制采用了 Commonjs 规范,弥补了当前 JavaScript...

  • 模块加载机制有哪些?es6中的module和其他机制有什么区别?

    模块加载机制有哪些?es6中的module和其他机制有什么区别? 答案: 模块机制有amd,cmd和commonJ...

  • 模块机制

    nodejs模块采用Commonjs规范,弥补Javascript开发大型应用没有标准的缺陷,类似java中的类,...

  • 模块机制

    CommonJS 模块规范 模块引用 require() 方法,引入一个模块的 API 到当前上下文中 模块定义 ...

  • angular中使用第三方库

    一、TypeScript 模块机制 javascript从es5之前都缺少一种模块机制,无法通过js引入文件,于是...

  • Nodejs2 模块机制

    模块 nodejs采用的是commonjs的模块机制,commonjs模块的定义很简单,主要分为模块引用requi...

  • Node模块机制

    Q&A 1.node模块机制有什么用?答:node模块机制使你可以选择要暴露给引用这个模块的程序的变量、函数或对...

  • 那些年成为node攻城狮的路(二)

    模块加载机制浅析 node中模块分为核心模块和文件模块两大类,核心模块诸如fs、http、util...,文件模块...

  • 饿了吗大前端阅读(二)

    模块 模块机制 模块加载机制官方文档已经说的很明白的,基本的流程是 如果加载为第三方或者系统依赖(就是在node_...

网友评论

      本文标题:模块机制

      本文链接:https://www.haomeiwen.com/subject/pascyktx.html