CommonJS 的 require 加载过程是怎样的?
require 的执行过程
require(X)
→ 路径解析(Module._resolveFilename)
→ 检查缓存(Module._cache)
→ 创建模块实例
→ 编译执行(Module.prototype._compile)
→ 返回 module.exports1. 路径解析
require('express') // 从 node_modules 查找
require('./lib') // 相对路径
require('/abs/path') // 绝对路径
require('path') // 内置模块查找顺序:
1. 内置模块(fs, path, http...)
2. node_modules/(从当前目录向上逐级查找)
./node_modules → ../node_modules → ../../node_modules → ...
3. 文件扩展名补全:.js → .json → .node
4. 目录:package.json 的 main 字段 → index.js2. 模块缓存
// 模块首次加载后缓存在 require.cache 中
const a1 = require('./lib')
const a2 = require('./lib')
console.log(a1 === a2) // true(同一个实例)
// 清除缓存
delete require.cache[require.resolve('./lib')]3. 模块包装
Node.js 将模块代码包裹在函数中:
// 原始模块代码
const x = 1
module.exports = { x }
// Node.js 实际执行
(function(exports, require, module, __filename, __dirname) {
const x = 1
module.exports = { x }
})这解释了为什么模块中有 require、module、__dirname 等变量。
4. module.exports vs exports
// module.exports 和 exports 初始指向同一对象
exports === module.exports // true
// ✅ 添加属性
exports.add = function() {} // 等同于 module.exports.add = ...
module.exports.add = function() {} // 同上
// ✅ 整体替换
module.exports = { add: function() {} }
// ❌ 错误:exports 不再指向 module.exports
exports = { add: function() {} } // 无效!require 返回空对象手写简化 require
function myRequire(filename) {
// 1. 解析绝对路径
const path = require.resolve(filename)
// 2. 检查缓存
if (myRequire.cache[path]) {
return myRequire.cache[path].exports
}
// 3. 创建模块
const module = { exports: {} }
myRequire.cache[path] = module
// 4. 包装执行
const wrapper = Module.wrap(fs.readFileSync(path, 'utf-8'))
const compiledFn = new Function('module', 'exports', 'require', wrapper)
compiledFn(module, module.exports, myRequire)
return module.exports
}
myRequire.cache = {}关键特性
- 同步加载:require 会阻塞直到模块加载完成
- 单例模式:模块只执行一次,后续 require 返回缓存
- 值的拷贝:导出的是值的复制,不是引用