← 返回目录

第 3 卷

第 3 / 12 卷日期:

示例(模块导入):

Code block

1// 模块B:b.js 2// 导入模块A

3const moduleA = require('./a.js');

4 5// 使用模块A导出的内容

6console.log(moduleA.name); // 输出:Node.js模块化 7moduleA.sayHello(); // 输出:Hello CommonJS

8 9// 若模块A直接导出函数

10const fn = require('./a.js');

11console.log(fn()); // 输出:直接导出一个函数

  1. 核心总结
  • 每个文件=一个模块,拥有独立作用域;
  • 导出: exports 是 module.exports 的引用,最终导出以 module.exports 为准;
  • 导入: require() 按特定规则查找模块,缓存模块结果,返回 module.exports 对象;
  • 执行特性:模块仅在首次导入时执行一次,后续导入直接使用缓存。

总结

  1. Node.js是基于V8引擎的服务器端JS运行环境,通过单线程+事件驱动+libuv实现高效并发;
  2. 核心全局对象有 global、 console、 process、 Buffer 等,各有明确的实用场景;
  3. 事件循环是异步核心,分6个阶段执行回调,微任务优先级高于宏任务;
  4. 非阻塞I/O让主线程不等待I/O操作,通过线程池+事件循环处理异步回调;
  5. Node.js默认使用CommonJS模块化, exports/module.exports 负责导出, require()

负责导入(带缓存和查找机制)。

六、从源码级别解释Node.js的模块加载机制

  1. 模块加载核心入口:Module类

Node.js源码中,模块加载的核心逻辑封装在 lib/module.js 的 Module 类中,每个模块都是 Module 的实例。核心属性包括: id(模块唯一标识)、 exports(模块导出对象)、

1.Module._resolveFilename 步骤1:模块路径解析()

源码中通过 Module._resolveFilename(request, parent, isMain) 方法解析模块的真实文件路径。核心逻辑:

  • 优先处理核心模块(如 fs、 path),直接返回内置模块路径;
  • 若为相对/绝对路径,结合父模块路径( parent.filename)拼接为完整路径,补全文件后缀

(.js、.json、.node);

  • 若为第三方模块,调用 lib/path.js 的路径查找逻辑,从当前目录 node_modules 开始递

归向上查找,直到找到模块或抵达根目录;

  • 解析过程中会处理符号链接( fs.realpathSync),确保路径真实有效。

2.Module._cache 步骤2:模块缓存检查()

Node.js源码中通过全局对象 Module._cache(一个对象,key为模块真实路径,value为 Module实例)缓存已加载的模块。

  • 解析出真实路径后,先检查 Module._cache 是否存在该模块实例;
  • 若存在,直接返回缓存的 module.exports,避免重复加载;
  • 若不存在,创建新的 Module 实例,存入缓存(提前缓存,防止循环依赖时重复创建)。

3.Module.load 步骤3:模块加载()

调用 module.load(filename) 方法加载模块,核心是根据文件类型选择对应的加载器(源码中定义在 Module._extensions 对象中):

  • .js 文件:通过 fs.readFileSync 同步读取文件内容,调用 Module._compile 编译执

行;

  • .json 文件:读取内容后通过 JSON.parse 解析为对象,赋值给 module.exports;
  • .node 文件:调用C++扩展模块的加载逻辑( process.dlopen),加载二进制模块。
  • 封装代码:将模块原始代码包裹在一个匿名函数中,函数参数为 exports、 require、

module、 __filename、 __dirname,确保模块作用域隔离(避免污染全局);

封装后的代码格式示例(源码动态拼接字符串):

(function (exports, require, module, __filename, __dirname) { // 模块原始代码

const fs = require('fs');

exports.name = 'test';

});

  • 执行函数:通过 vm.runInThisContext(Node.js的虚拟机模块)执行封装后的函数,传入

当前模块的 exports、 require 等参数;

  • 执行过程中,模块内对 exports 或 module.exports 的修改会直接作用于Module实例的对

应属性。

5.module.exports 步骤5:返回导出对象()

模块编译执行完成后,将 module.exports 作为 require() 的返回值,供父模块使用。

  1. 循环依赖的源码处理逻辑

当A模块依赖B模块,B模块又依赖A模块时,源码通过“提前缓存+部分导出”解决:

  • A模块加载时,创建A的Module实例并存入缓存,然后加载B模块;
  • B模块加载时依赖A,从缓存中取出A的实例(此时A尚未执行完成, module.exports 可能是初始

空对象);

  • B模块执行完成后导出自己的 exports,A模块继续执行,补全自己的 module.exports;
  • 源码中通过 module.loaded 属性标记模块是否执行完成,确保循环依赖时不会死锁。

七、什么是中间件 (middleware)?在Express、Koa中如何使用中间件?

  1. 中间件的核心定义

中间件是Web框架(如Express、Koa)中用于处理HTTP请求的“管道式”组件,本质是一个函数。

它可以:

  • 接收请求对象( req)、响应对象( res)、后续中间件的回调函数(Express)或上下文对象
  • ctx)、next函数(Koa);
  • 对请求进行预处理(如解析参数、验证权限、记录日志);
  • 终止请求-响应循环(如直接返回响应);
  • 调用下一个中间件(通过 next()),形成处理链条。
全站分卷索引 · 12 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷
  5. 5第 5 卷
  6. 6第 6 卷
  7. 7第 7 卷
  8. 8第 8 卷
  9. 9第 9 卷
  10. 10第 10 卷
  11. 11第 11 卷
  12. 12第 12 卷