面试题知识-NodeJS系列
模块加载机制
问题:Node.js中require
的加载顺序是怎样的?
Node.js模块加载遵循以下规则:
- 核心模块(如
fs
、path
)优先加载,无需路径。 - 非核心模块按以下顺序解析:
- 当前目录下的
node_modules
。 - 向上递归查找父目录的
node_modules
,直到根目录。
- 当前目录下的
- 文件路径加载时,依次尝试
.js
、.json
、.node
扩展名。
示例代码:
// 加载自定义模块
const myModule = require('./myModule');
CommonJS与ES Modules区别
问题:对比CommonJS和ES Modules的差异
语法差异
- CommonJS使用
require()
和module.exports
。 - ES Modules使用
import/export
语法。
- CommonJS使用
加载时机
- CommonJS动态加载,运行时解析。
- ES Modules静态加载,编译时分析依赖。
缓存机制
- CommonJS模块首次加载后缓存结果。
- ES Modules通过引用绑定实时更新。
代码对比:
// CommonJS
module.exports = { key: 'value' };
const lib = require('lib');// ES Modules
export const key = 'value';
import { key } from 'lib';
模块缓存机制
问题:如何避免模块被重复加载?
Node.js通过require.cache
对象缓存已加载模块。键为模块路径,值为模块对象。
清除缓存方法:
delete require.cache[require.resolve('module')];
循环依赖处理
问题:如何处理模块间的循环依赖?
Node.js对循环依赖的解决策略:
- 加载模块A时,先将其
exports
对象放入缓存。 - 若模块A依赖模块B,而模块B又依赖模块A,则模块B获取的是模块A的未完成副本。
示例场景:
// a.js
exports.done = false;
const b = require('./b');
console.log('在a中,b.done =', b.done);
exports.done = true;// b.js
exports.done = false;
const a = require('./a');
console.log('在b中,a.done =', a.done);
exports.done = true;
核心模块原理
问题:Node.js核心模块(如fs
)如何被加载?
- 核心模块编译进Node.js二进制文件,加载时无需磁盘I/O。
- 通过
process.binding()
连接C++层与JavaScript层。
自定义模块开发
问题:如何发布一个可复用的Node.js模块?
- 初始化
package.json
:npm init
- 定义入口文件(如
index.js
)并导出功能。 - 发布到npm仓库:
npm publish
热更新实现
问题:如何在运行时动态更新模块?
- 监听文件变化(如
fs.watch
)。 - 清除旧模块缓存并重新加载:
const path = './module.js'; fs.watch(path, () => {delete require.cache[require.resolve(path)];require(path); });
性能优化
问题:如何优化模块加载性能?
- 减少同步加载(避免
require
阻塞事件循环)。 - 使用
import()
动态加载ES Modules(Node.js 12+支持)。 - 合理拆分模块,避免过大的单一文件。
动态加载示例:
const module = await import('./module.mjs');