面试题答案
一键面试Node.js模块加载机制
- 模块查找路径
- 核心模块:Node.js内置了许多核心模块,如
http
、fs
等。当使用require('http')
这样的语句加载模块时,Node.js首先会检查是否为核心模块。核心模块的优先级最高,直接从Node.js的安装目录下的lib
文件夹中加载,无需从文件系统查找。 - 文件模块:如果不是核心模块,Node.js会按照当前文件所在目录查找。例如,在
/project/app.js
中require('./module1')
,Node.js会先在/project
目录下查找module1.js
、module1.json
、module1.node
文件。- 如果找不到
module1.js
,会查找module1.json
,module1.json
文件需要是JSON格式,并且被require
时会被解析为JavaScript对象。 - 如果还找不到,会查找
module1.node
,.node
文件是用C或C++编写的Addon模块,会被加载并执行。
- 如果找不到
- Node_modules文件夹:如果在当前目录下未找到,Node.js会向上级目录查找
node_modules
文件夹。例如,在/project/sub - dir/app.js
中require('module2')
,会先在/project/sub - dir/node_modules
查找module2
,如果没有则在/project/node_modules
查找,依此类推,直到根目录的node_modules
。 - 全局安装模块:如果在上述路径都未找到,Node.js会查找全局安装的模块。全局模块安装路径一般是在Node.js安装目录下的
lib/node_modules
(不同操作系统可能略有差异)。例如,通过npm install -g some - module
全局安装的模块可以被任何项目require
。
- 核心模块:Node.js内置了许多核心模块,如
- 缓存处理
- 首次加载:当一个模块首次被
require
时,Node.js会执行模块代码,并将模块的导出对象缓存起来。 - 后续加载:当后续再次
require
同一个模块时,Node.js会直接从缓存中返回该模块的导出对象,而不会再次执行模块代码。这提高了模块加载的效率,避免了重复执行模块代码带来的性能开销。
- 首次加载:当一个模块首次被
循环依赖处理
- 示例代码1
// a.js console.log('a starting'); const b = require('./b'); console.log('a finished'); exports.a = 'a'; // b.js console.log('b starting'); const a = require('./a'); console.log('b finished'); exports.b = 'b';
- 处理过程:
- 当
a.js
被require
时,a.js
开始执行,输出a starting
。 - 当执行到
const b = require('./b')
时,b.js
开始执行,输出b starting
。 - 当
b.js
执行到const a = require('./a')
时,由于a.js
正在加载过程中,Node.js不会再次执行a.js
的代码,而是返回一个不完整的a
模块对象(此时exports.a
还未赋值)。 b.js
继续执行,输出b finished
,并导出b
对象。- 回到
a.js
,继续执行,输出a finished
,并导出a
对象。
- 当
- 处理过程:
- 示例代码2
// main.js const a = require('./a'); console.log(a); // a.js const b = require('./b'); exports.a = 'a'; // b.js const a = require('./a'); exports.b = 'b';
- 处理过程:
main.js
require('./a')
,a.js
开始执行。a.js
require('./b')
,b.js
开始执行。b.js
require('./a')
,返回正在加载的a
模块的不完整对象(此时exports.a
未赋值)。b.js
导出b
对象。a.js
继续执行,导出a
对象。main.js
获取并输出a
模块,此时a
模块是完整的,包含exports.a
。
- 处理过程:
通过这种方式,Node.js能够处理模块间的循环依赖,保证程序的正常运行,尽管在遇到循环依赖时,可能需要注意模块导出对象的赋值顺序等问题,以确保程序逻辑正确。