Node.js 面试题详细答案 - Q7
Q7: require 的模块加载机制是什么?模块缓存是如何工作的?
require 模块加载机制
基本流程
模块路径解析
const fs = require('fs')
const utils = require('./utils')
const config = require('../config')
const app = require('/path/to/app')
const express = require('express')
const lodash = require('lodash')
模块查找顺序
const express = require('express')
const lodash = require('lodash')
模块缓存机制
缓存的基本原理
console.log('缓存对象:', require.cache)
const module1 = require('./my-module')
console.log('第一次加载:', module1)
const module2 = require('./my-module')
console.log('第二次加载:', module2)
console.log('是否相同:', module1 === module2)
缓存示例
console.log('模块被加载了')
let count = 0
module.exports = {
increment: () => ++count,
getCount: () => count,
}
console.log('=== 第一次加载 ===')
const module1 = require('./my-module')
console.log('计数:', module1.getCount())
console.log('=== 第二次加载 ===')
const module2 = require('./my-module')
console.log('计数:', module2.getCount())
缓存键值
const path = require('path')
console.log('当前文件路径:', __filename)
console.log('缓存键:', path.resolve(__filename))
const cacheKey = path.resolve('./my-module.js')
console.log('是否在缓存中:', cacheKey in require.cache)
模块加载详细过程
1. 路径解析
const path = require('path')
function resolveModulePath(modulePath) {
if (modulePath.startsWith('./') || modulePath.startsWith('../')) {
return path.resolve(path.dirname(__filename), modulePath)
}
return modulePath
}
console.log('解析路径:', resolveModulePath('./utils'))
console.log('解析路径:', resolveModulePath('../config'))
2. 文件扩展名处理
const config1 = require('./config')
const config2 = require('./config.js')
const config3 = require('./config.json')
3. 模块编译执行
{
"name": "my-app",
"version": "1.0.0"
}
const config = require('./config.json');
console.log(config.name);
模块缓存管理
查看缓存
console.log('缓存的模块:')
Object.keys(require.cache).forEach((key) => {
console.log(key)
})
const modulePath = require.resolve('./my-module')
console.log('模块路径:', modulePath)
console.log('是否在缓存中:', modulePath in require.cache)
清除缓存
const modulePath = require.resolve('./my-module')
delete require.cache[modulePath]
const freshModule = require('./my-module')
console.log('重新加载的模块:', freshModule)
热重载示例
function hotReload(modulePath) {
const resolvedPath = require.resolve(modulePath)
delete require.cache[resolvedPath]
return require(modulePath)
}
let myModule = require('./my-module')
const fs = require('fs')
fs.watchFile('./my-module.js', () => {
console.log('模块文件变化,重新加载...')
myModule = hotReload('./my-module')
})
循环依赖处理
循环依赖示例
console.log('a.js 开始加载')
const b = require('./b')
console.log('a.js 中 b 的值:', b)
module.exports = { name: 'a' }
console.log('b.js 开始加载')
const a = require('./a')
console.log('b.js 中 a 的值:', a)
module.exports = { name: 'b' }
const a = require('./a')
console.log('main.js 中 a 的值:', a)
避免循环依赖
console.log('a.js 开始加载')
module.exports = {
name: 'a',
getB: () => require('./b'),
}
console.log('b.js 开始加载')
module.exports = {
name: 'b',
getA: () => require('./a'),
}
性能优化
模块预加载
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
条件加载
function loadModule(condition) {
if (condition) {
return require('./heavy-module')
} else {
return require('./light-module')
}
}
总结
- 加载机制:路径解析 → 缓存检查 → 文件加载 → 编译执行 → 缓存存储
- 缓存机制:基于绝对路径的缓存,避免重复加载
- 查找顺序:核心模块 → 相对路径 → node_modules
- 文件类型:支持 .js, .json, .node 文件
- 循环依赖:通过缓存机制处理,但可能导致不完整导出
- 性能优化:利用缓存机制,合理设计模块结构