CommonJS 的 require 加载过程是怎样的?

require 的执行过程

require(X) 
  → 路径解析(Module._resolveFilename)
  → 检查缓存(Module._cache)
  → 创建模块实例
  → 编译执行(Module.prototype._compile)
  → 返回 module.exports

1. 路径解析

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.js

2. 模块缓存

// 模块首次加载后缓存在 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 = {}

关键特性

同分类其他题目