这一小节相对简短,但非常实用。朴灵作者用一个典型的模块依赖结构来展示Node.js中require的调用层级(也叫模块调用栈或依赖树),帮助我们理解模块之间的依赖关系、加载顺序,以及为什么循环依赖不会导致死锁。核心目的是让你可视化“入口文件 → 用户模块 → 核心模块”的层级结构。
详细讲解
模块调用栈的典型结构
书里用一个金字塔形的层级图来描述(很多笔记都重绘了这个图):
- 最底层:核心模块(如fs、http、buffer等)。这些是Node的“地基”,优先加载,性能最高。
- 中间层:文件模块或第三方模块(我们require的自定义.js文件,或npm包)。这些依赖核心模块。
- 最顶层:主模块(入口文件,通常是node app.js时的app.js)。它require中间层模块,从而间接依赖底层。
典型调用栈示例:
主模块 (app.js)
├── 用户模块 A (a.js) // require('./a')
│ ├── 用户模块 B (b.js) // require('./b')
│ │ └── 核心模块 fs // require('fs')
│ └── 核心模块 http // require('http')
└── 第三方模块 express // require('express') → 内部依赖http等核心模块
加载顺序和缓存的作用
- 从顶层主模块开始,自顶向下加载。
- 遇到require,先查缓存 → 无则路径分析 → 编译执行 → 缓存exports。
- 因为缓存,同一个模块在调用栈的不同位置只会执行一次(这解决了循环依赖:A require B,B require A → B先得到A的空exports对象,执行完后再填充)。
实际意义
- 调试时:理解依赖链,能快速定位问题(如某个核心模块出错影响上层)。
- 性能优化:减少深层依赖、避免循环。
- 与循环依赖的关联:书里在这里顺带强调,CommonJS的缓存机制让循环依赖“安全”(虽不推荐,但不会崩溃)。