Node知识 --- 模块

136 阅读4分钟

NodeJs基本遵循了commonJs的规范,对模块规范进行了取舍,增加了少许自身需用的特性。

Tips: require引入模块,是一个同步的过程。

- 模块分类

分为Node提供的核心模块文件模块自定义模块(Npm包)

1. 核心模块:分为由C/C++编写部分 与 js编写部分。

通常为了好区分使用node:的形式引入(require实现代码中,有针对node:形式的判断为核心模块),
例如 require('node:fs'),

其中C/C++部分存放在源代码的src目录,js部分存放在lib目录与deps/**/*.js。
由C/C++编写的部分称为`内建模块`,通常不会被用户直接调用,而是调用相关js模块。
Node中的fs,buffer, cryto,os等都是部分用C/C++编写的。
例如lib目录下有`fs.js`文件, 在src会有`node_fs.cc`文件

核心模块最牛的是v8 与libuv, 
其中v8作为解释器引擎,libuv则是与底层相关的模块,libuv处理Node中的异步IO,与系统打交道。

2. 文件模块: 以`.``..`开始的相对路径和以`/`开始的绝对路径的文件。

3. 自定义模块(Npm包)

- 引入模块的步骤

  1. 路径分析 2. 文件定位 3. 编译执行

- 核心模块:
   Node源代码编译的时候,对于C/C++那部分会被编译为二进制代码。
   对于js那部分,会通过js2c.py以字符串的形式放到node命名空间中。

   当node进程启动的时候, 对于C/C++编译的二进制代码存到内存中,node命名空间的js字符串会被缓存到内存。

   当引入核心模块时候,会省略 文件定位与编译执行 步骤,
   在路径分析中会优先判断,所以加载速度最快。

- 文件模块:
   会在路径分析的时候把相对路径为绝对路径,并以真实路径作为key,编译执行结果作为value,存放到缓存中,
   方便下次更快加载。
   因为路径转为绝对路径后明确了文件的位置,所以加载速度相对核心模块慢,但是比自定义模块快。
   
   对于标识符中没有扩展名的情况查找方式如下:
    * 在路径分析过程中,node会按照.js, .json, .node补足扩展名
    * 然后依次用fs文件模块同步阻塞的去判断文件是否存在(比如fs.access, fs.stat等方法)
    const { access, stat } = require("fs");
    access("./common-module.js", (err) => {
        if (!err) {
            console.log("exists");
        }
    });

- 自定义模块:
    在Linux下,执行module.paths的打印,得到如下结果:
   [
   '/Volumes/workplace/study/node/node_modules', // 当前目录的node_modules
   '/Volumes/workplace/study/node_modules',      // 上级目录的node_modules
   '/Volumes/workplace/node_modules',            // 上级的上级目录的node_modules
   '/Volumes/node_modules',                     // 上级的上级的上级目录的node_modules
   '/node_modules'                              // 上级的上级的上级的上级目录的node_modules
   ]
   可看出,当模块的所处层级深的时候,查找路径的方式比较耗时,这也是自定义模块加载速度最慢的原因。
   对于自定义模块查找的分析如下:
     * 查找是目录的时候,node会认为它是一个npm包,
     * 首先去找package.json,通过JSON.parse去解析出里面的main ,看main指向的文件,
     * 如果main指向错误,或者不存在该指向文件,或者没有package.json, 则去找该目录下的index文件,
     * 可能是index.js, index.json, index.node
     * 如果没有,则抛出错误
   

- 模块的缓存

  • 核心js模块缓存: process.binding("natives") 可取到

 js核心模块代码在node源代码编译过程中,经过js2c.py以字符串的形式存储到node命名空间中(不能直接执行)。
 node启动时候,上述字符串形式的js核心代码被加载到内存中,存储到NativeModule._cache中
 当利用到相关js核心模块时候,例如const fs = require('fs'),会直接定位到内存中的相关缓存,
 然后执行内存中相关js的代码,并返回执行结果对象。
 下次利用fs的时候,直接返回该执行结果对象。    
  • 文件模块缓存:require.cache 或者Module._cache可取到
const testModule = require("test-module"); 
const { Module } = require("module");
console.log("module cache", Module._cache);
console.log("module cache", require.cache);

console.log("cache", require.cache === Module._cache); // 返回true

// require的代码实现中,对require.cache 指向了 Module._cache
/** 

- 更好的理解