[NodeJs系列]NodeJs模块机制

栏目: Node.js · 发布时间: 5年前

内容简介:注: 1. 本文涉及的nodejs源码如无特别说明则全部基于欢迎关注公众号:本节主要基于NodeJs源码,对其模块的实现做一个简要的概述,如有错漏,望诸君不吝指正。

注: 1. 本文涉及的nodejs源码如无特别说明则全部基于 v10.14.1

欢迎关注公众号: 前端情报局

Nodejs 中对模块的实现

本节主要基于NodeJs源码,对其模块的实现做一个简要的概述,如有错漏,望诸君不吝指正。

当我们使用 require 引入一个模块的时候,概况起来经历了两个步骤:路径分析和模块载入

路径分析

路径分析其实就是模块查找的过程,由 _resolveFilename 函数实现。

我们通过一个例子,展开说明:

const http = require('http');
const moduleA = requie('./parent/moduleA');

这个例子中,我们引入两种不同类型的模块:核心模块- http 和自定义模块 moduleA

对于核心模块而言, _resolveFilename 会跳过查找步骤,直接返回,交给下一步处理

if (NativeModule.nonInternalExists(request)) {
    // 这里的request 就是模块名称 'http'
    return request;
}

而对于自定义模块而言,存在以下几种情况( _findPath

  1. 文件模块
  2. 目录模块
  3. 从node_modules目录加载
  4. 全局目录加载

这些在 官方文档 中已经阐述的很清楚了,这里就不再赘述。

如果模块存在,那么 _resolveFilename 会返回该模块的绝对路径,比如 /Users/xxx/Desktop/practice/node/module/parent/moduleA.js

载入模块

获取到模块地址后,Node就开始着手载入模块。

首先,Node会查看模块是否存在缓存中:

// filename 即模块绝对路径
var cachedModule = Module._cache[filename];
if (cachedModule) {
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
}

存在则返回对应缓存内容,不存在则进一步判断该模块是否是核心模块:

if (NativeModule.nonInternalExists(filename)) {
    return NativeModule.require(filename);
}

如果模块既不存在于缓存中也非核心模块,那么Node会实例化一个全新的模块对象

function Module(id, parent){
  // 通常是模块绝对路径
  this.id = id;
  // 要导出的内容
  this.exports = {};
  // 父级模块
  this.parent = parent;
  this.filename = null;
  // 是否已经加载成功
  this.loaded = false;
  // 子模块
  this.children = [];
}

var module = new Module(filename, parent);

而后Node会根据路径尝试载入。

function tryModuleLoad(module, filename) {
  var threw = true;
  try {
    module.load(filename);
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
    }
  }
}

对于不同的文件扩展名,其载入方法也有所不同。

通过fs同步读取文件内容后将其包裹在指定函数中:

Module.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];

调用执行此函数:

compiledWrapper.call(this.exports, this.exports, require, this,
                                  filename, dirname);
  • .json文件

通过fs同步读取文件内容后,用 JSON.parse 解析并返回内容

var content = fs.readFileSync(filename, 'utf8');
try {
    module.exports = JSON.parse(stripBOM(content));
} catch (err) {
    err.message = filename + ': ' + err.message;
    throw err;
}
  • .node

这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件。

return process.dlopen(module, path.toNamespacedPath(filename));
  • .mjs

这是用于处理ES6模块的扩展文件,是NodeJs在v8.5.0后新增的特性。对于这类扩展名的文件,只能使用ES6模块语法 import 引入,否则将会报错(启用 --experimental-modules 的情况下)

throw new ERR_REQUIRE_ESM(filename);

如果一切顺利,就会返回附加在exports对象上的内容

return module.exports;

模块循环依赖

接下来我们来探究一下模块循环依赖的问题:模块1依赖模块2,模块2依赖模块1,会发生什么?

这里只探究commonjs的情况

为此,我们创建了两个文件,module-a.js和module-b.js,并让他们相互引用:

module-a.js

console.log(' 开始加载 A 模块');
exports.a = 2;
require('./module-b.js');
exports.b = 3;
console.log('A 模块加载完毕');

module-b.js

console.log(' 开始加载 B 模块');
let moduleA = require('./module-a.js');
console.log(moduleA.a,moduleA.b)
console.log('B 模块加载完毕');

运行 module-a.js ,可以看到控制台输出:

开始加载 A 模块
开始加载 B 模块
2 undefined
B 模块加载完毕
A 模块加载完毕

这时因为每个 require 都是同步执行的,在 module-a 完全加载前需要先加载 ./module-b ,此时对于 module-a 而言,其 exports 对象上只附加了属性 a ,属性 b 是在 ./module-b 加载完成后才赋值的。

QA

  1. 如何删除模块缓存?

可以通过 delete require.cache(moduleId) 来删除对应模块的缓存,其中moduleId表示的是模块的绝对路径,一般的,如果我们需要对某些模块进行热更新,可以使用此特性,举个例子:

// hot-reload.js
console.log('this is hot reload module');

// index.js
const path = require('path');
const fs = require('fs');
const hotReloadId = path.join(__dirname,'./hot-reload.js');
const watcher = fs.watch(hotReloadId);
watcher.on('change',(eventType,filename)=>{
    if(eventType === 'change'){
        delete require.cache[hotReloadId];
        require(hotReloadId);
    }
});
  1. Node中可以使用ES6 模块吗?

从8.5.0版本开始,NodeJs开始支持原生ES6模块,启用该功能需要两个条件:

  1. 所有使用ES6模块的文件扩展名都必须是.mjs
  2. 命令行选项--experimental-modules

node --experimental-modules index.mjs

node --experimental-modules index.mjs

但是截止到NodeJs v10.15.0,ES6模块的支持依旧是实验性的,笔者并不推荐在公司项目中使用

参考

  1. nodejs-loader.js
  2. 朴灵. 深入浅出Node.js

[NodeJs系列]NodeJs模块机制


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Computation and Programming Using Python

Introduction to Computation and Programming Using Python

John V. Guttag / The MIT Press / 2013-7 / USD 25.00

This book introduces students with little or no prior programming experience to the art of computational problem solving using Python and various Python libraries, including PyLab. It provides student......一起来看看 《Introduction to Computation and Programming Using Python》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具