内容简介:注: 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 )
- 文件模块
- 目录模块
- 从node_modules目录加载
- 全局目录加载
这些在 官方文档 中已经阐述的很清楚了,这里就不再赘述。
如果模块存在,那么 _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文件( _compile )
通过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
- 如何删除模块缓存?
可以通过 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); } });
- Node中可以使用ES6 模块吗?
从8.5.0版本开始,NodeJs开始支持原生ES6模块,启用该功能需要两个条件:
- 所有使用ES6模块的文件扩展名都必须是.mjs
- 命令行选项--experimental-modules
node --experimental-modules index.mjs
node --experimental-modules index.mjs
但是截止到NodeJs v10.15.0,ES6模块的支持依旧是实验性的,笔者并不推荐在公司项目中使用
参考
- nodejs-loader.js
- 朴灵. 深入浅出Node.js
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 详解Node模块加载机制
- Node.js 系列 - 模块机制
- [NodeJs系列]NodeJs模块机制
- node学习篇02-模块机制
- 浅谈node.js模块引入机制
- 深入浅出node.js总结-模块机制(1)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机组成(第 6 版)
Andrew S. Tanenbaum、Todd Austin / 刘卫东、宋佳兴 / 机械工业出版社 / 2014-8-19 / CNY 99.00
本书采用结构化方法来介绍计算机系统,书的内容完全建立在“计算机是由层次结构组成的,每层完成规定的功能”这一概念之上。作者对本版进行了彻底的更新,以反映当今最重要的计算机技术以及计算机组成和体系结构方面的最新进展。书中详细讨论了数字逻辑层、微体系结构层、指令系统层、操作系统层和汇编语言层,并涵盖了并行体系结构的内容,而且每一章结尾都配有丰富的习题。本书适合作为计算机专业本科生计算机组成与结构课程的教......一起来看看 《计算机组成(第 6 版)》 这本书的介绍吧!