全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-15 8 分钟 ✍️ juanwangdev

Node.js 模块加载机制与缓存

理解模块加载机制和缓存,有助于优化性能和避免循环依赖问题。

模块加载流程

当 require 一个模块时,Node.js 按以下步骤处理:

  1. 解析模块路径
  2. 检查缓存
  3. 加载模块内容
  4. 包装模块代码
  5. 执行模块代码
  6. 返回 exports 对象

模块路径解析

JavaScript
// 核心模块:直接加载
const fs = require('fs');        // 加载内置 fs 模块
const path = require('path');    // 加载内置 path 模块

// 相对路径:基于当前文件
const utils = require('./utils');   // ./utils.js
const config = require('../config'); // ../config.js

// 绝对路径:直接加载
const mod = require('/home/user/module.js');

// 第三方模块:查找 node_modules
const express = require('express'); // 从 node_modules 加载

node_modules 查找顺序

JavaScript
// 加载 'lodash' 时的查找顺序
// ./node_modules/lodash
// ../node_modules/lodash
// ../../node_modules/lodash
// ... 直到根目录或全局 node_modules

查看查找路径:

JavaScript
console.log(module.paths);
// [
//   '/home/user/project/node_modules',
//   '/home/user/node_modules',
//   '/home/node_modules',
//   '/node_modules'
// ]

模块缓存机制

模块首次加载后会被缓存,后续 require 返回缓存实例:

JavaScript
// counter.js
let count = 0;
module.exports = {
  increment: () => ++count,
  getCount: () => count
};

// app.js
const counter1 = require('./counter');
counter1.increment(); // count = 1

const counter2 = require('./counter');
console.log(counter2.getCount()); // 1(同一实例)

console.log(counter1 === counter2); // true

查看缓存

JavaScript
// 查看所有缓存模块
console.log(require.cache);

// 查看特定模块缓存
const modulePath = require.resolve('./utils');
console.log(require.cache[modulePath]);

清除缓存

JavaScript
// 清除单个模块缓存
delete require.cache[require.resolve('./config')];

// 重新加载模块
const config = require('./config'); // 重新执行模块代码

// 清除所有缓存(谨慎使用)
Object.keys(require.cache).forEach(key => {
  delete require.cache[key];
});

循环依赖

JavaScript
// a.js
const b = require('./b');
console.log('a.js: b.name =', b.name);
exports.name = 'A';

// b.js
const a = require('./a'); // a.js 未完成,得到部分 exports
console.log('b.js: a.name =', a.name); // undefined
exports.name = 'B';

// main.js
const a = require('./a');
// 输出: b.js: a.name = undefined
// 输出: a.js: b.name = B

Node.js 处理循环依赖:返回模块未完成时的 exports 副本。

避免循环依赖

JavaScript
// 方案1:延迟加载
// a.js
exports.name = 'A';
exports.getB = () => require('./b'); // 延迟到调用时加载

// 方案2:提取公共模块
// common.js
exports.shared = { /* 共享数据 */ };

// a.js 和 b.js 都引入 common.js

require.resolve

JavaScript
// 解析模块路径,不加载模块
const path = require.resolve('./utils');
console.log(path); // /home/user/project/utils.js

// 用于检查模块是否存在
try {
  require.resolve('./config');
  console.log('模块存在');
} catch (err) {
  console.log('模块不存在');
}

模块包装

Node.js 会将模块代码包装在函数中:

JavaScript
// 原模块代码
const a = 1;
module.exports = a;

// 实际执行(包装后)
(function(exports, require, module, __filename, __dirname) {
  const a = 1;
  module.exports = a;
});

这解释了为什么 module、exports、require、__filename、__dirname 在模块中可用。

要点总结

  • 模块首次加载后缓存,后续 require 返回缓存实例
  • node_modules 从当前目录向上查找直到根目录
  • 循环依赖返回未完成的 exports 副本,应避免
  • require.resolve 解析路径但不加载模块
  • 模块代码被包装,exports/require/module 等是注入参数

📝 发现内容有误?点击此处直接编辑

← 上一篇 Node.js 创建自定义模块
下一篇 → Node.js 模块系统概述
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库