博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[NodeJs系列]NodeJs模块机制
阅读量:7098 次
发布时间:2019-06-28

本文共 3588 字,大约阅读时间需要 11 分钟。

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

如果你对NodeJs系列感兴趣,欢迎关注微信公众号:前端神盾局或

Nodejs 中对模块的实现

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

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

路径分析

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

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

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

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

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

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

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

  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];    }  }}

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

  • .js文件()

通过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 undefinedB 模块加载完毕A 模块加载完毕

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

QA

  1. 如何删除模块缓存?

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

// hot-reload.jsconsole.log('this is hot reload module');// index.jsconst 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. 朴灵. 深入浅出Node.js

image

转载地址:http://eyeql.baihongyu.com/

你可能感兴趣的文章
java web中Excel文件导入
查看>>
framwork
查看>>
MySql常用命令总结
查看>>
RSA算法原理(二)
查看>>
情景英语-美国情景会话大全 精选
查看>>
spring 事务代理的方法
查看>>
定制属于自己的linux
查看>>
我的友情链接
查看>>
nginx upstrea分配方式
查看>>
Java类加载器(死磕3)
查看>>
我的友情链接
查看>>
mysql启动报错和主从同步报错解决方法
查看>>
运用内核创建一个小的linux【壹】--分析linux启动流程
查看>>
ERROR 2003 (HY000): Can't connect to MySQL server
查看>>
TCP/IP状态图 && TIME_WAIT作用
查看>>
我的友情链接
查看>>
Juniper 基于策略的×××
查看>>
Java项目命名规范
查看>>
【CCNA学习笔记】访问控制列表
查看>>
Java基础学习总结(19)——Java环境变量配置
查看>>