第 3 卷
示例(模块导入):
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()); // 输出:直接导出一个函数
- 核心总结
- 每个文件=一个模块,拥有独立作用域;
- 导出: exports 是 module.exports 的引用,最终导出以 module.exports 为准;
- 导入: require() 按特定规则查找模块,缓存模块结果,返回 module.exports 对象;
- 执行特性:模块仅在首次导入时执行一次,后续导入直接使用缓存。
总结
- Node.js是基于V8引擎的服务器端JS运行环境,通过单线程+事件驱动+libuv实现高效并发;
- 核心全局对象有 global、 console、 process、 Buffer 等,各有明确的实用场景;
- 事件循环是异步核心,分6个阶段执行回调,微任务优先级高于宏任务;
- 非阻塞I/O让主线程不等待I/O操作,通过线程池+事件循环处理异步回调;
- Node.js默认使用CommonJS模块化, exports/module.exports 负责导出, require()
负责导入(带缓存和查找机制)。
六、从源码级别解释Node.js的模块加载机制
- 模块加载核心入口: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() 的返回值,供父模块使用。
- 循环依赖的源码处理逻辑
当A模块依赖B模块,B模块又依赖A模块时,源码通过“提前缓存+部分导出”解决:
- A模块加载时,创建A的Module实例并存入缓存,然后加载B模块;
- B模块加载时依赖A,从缓存中取出A的实例(此时A尚未执行完成, module.exports 可能是初始
空对象);
- B模块执行完成后导出自己的 exports,A模块继续执行,补全自己的 module.exports;
- 源码中通过 module.loaded 属性标记模块是否执行完成,确保循环依赖时不会死锁。
七、什么是中间件 (middleware)?在Express、Koa中如何使用中间件?
- 中间件的核心定义
中间件是Web框架(如Express、Koa)中用于处理HTTP请求的“管道式”组件,本质是一个函数。
它可以:
- 接收请求对象( req)、响应对象( res)、后续中间件的回调函数(Express)或上下文对象
- ctx)、next函数(Koa);
- 对请求进行预处理(如解析参数、验证权限、记录日志);
- 终止请求-响应循环(如直接返回响应);
- 调用下一个中间件(通过 next()),形成处理链条。
全站分卷索引 · 12 卷
- 1第 1 卷
- 2第 2 卷
- 四、Node.js中的非阻塞I/O(Non-blocking I/O)模型
- • 阻塞I/O:主线程发起I/O操作后,暂停执行所有代码,直到I/O操作完成(如传统的PHP文件读
- • 非阻塞I/O:主线程发起I/O操作后,立即返回一个状态(未完成),并继续执行后续代码;I/O操作
- • exports:是 module.exports 的一个引用(快捷方式),默认是一个空对象 {},只能通
- • module.exports:是模块的真正导出对象,Node.js最终导出的是 module.exports 的
- ◦ 若为相对路径( ./、 ../)或绝对路径,直接按路径查找对应
- ◦ 若为第三方模块(如 express),会在当前目录的 node_modules 文件夹中查找,若找不
- 3第 3 卷
- 4第 4 卷
- 5第 5 卷
- 6第 6 卷
- 7第 7 卷
- 8第 8 卷
- 9第 9 卷
- 10第 10 卷
- 11第 11 卷
- 12第 12 卷