MST

星途 面试题库

面试题:JavaScript之Node.js中CommonJS模块系统的加载机制

阐述Node.js中CommonJS模块系统的模块加载过程,包括如何处理循环依赖,以及模块缓存是如何工作的。请结合具体代码示例说明循环依赖场景下的表现和解决方法。
28.4万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

CommonJS模块系统的模块加载过程

  1. 路径解析:当使用require引入模块时,Node.js首先会根据传入的参数解析模块路径。如果是核心模块(如fshttp等),直接加载核心模块。如果是相对路径(以./../开头)或绝对路径,会按照文件系统路径查找模块文件。如果是第三方模块,会在node_modules目录中查找。
  2. 文件定位:找到模块文件后,根据文件扩展名确定加载方式。.js文件会被解析为JavaScript代码,.json文件会被解析为JSON数据,.node文件会被作为C++插件加载。如果没有扩展名,Node.js会依次尝试添加.js.json.node扩展名进行查找。
  3. 模块编译:对于JavaScript模块,会将模块代码包装在一个函数中,这个函数的参数为exportsrequiremodule__filename__dirname。通过这种方式,每个模块都有自己独立的作用域,避免变量污染。然后执行这个包装后的函数,模块的导出内容会被添加到exports对象上。

循环依赖处理

在循环依赖场景下,Node.js会先加载被依赖模块的部分内容,然后返回一个不完整的模块对象。当被依赖模块执行完毕后,该模块对象才会完整。

模块缓存工作原理

Node.js使用一个全局的缓存对象来存储已经加载过的模块。当使用require加载模块时,首先会检查缓存中是否已经存在该模块。如果存在,直接返回缓存中的模块导出对象,而不会再次加载和执行模块代码。这样可以提高模块加载效率,避免重复加载。

代码示例说明循环依赖场景下的表现和解决方法

假设有两个模块a.jsb.js,它们相互依赖。

a.js

console.log('a.js starting');
const b = require('./b');
console.log('a.js requiring b');
exports.message = 'Hello from a';
console.log('a.js finished');

b.js

console.log('b.js starting');
const a = require('./a');
console.log('b.js requiring a');
exports.message = 'Hello from b';
console.log('b.js finished');

main.js

console.log('main.js starting');
const a = require('./a');
console.log('main.js requiring a');
const b = require('./b');
console.log('main.js requiring b');
console.log(a.message);
console.log(b.message);
console.log('main.js finished');

表现

  1. 当运行main.js时,a.js开始加载,然后a.js尝试加载b.js
  2. b.js开始加载,b.js又尝试加载a.js。由于a.js正在加载中,Node.js会返回一个不完整的a模块对象给b.js
  3. b.js继续执行,完成加载并导出。
  4. a.js继续执行,完成加载并导出。
  5. main.js继续执行,获取到完整的ab模块对象并输出信息。

解决方法: 尽量避免循环依赖。如果无法避免,可以将相互依赖的部分提取到一个独立的模块中,减少模块之间的直接循环引用。例如:

shared.js

exports.commonData = 'This is common data';

a.js

console.log('a.js starting');
const shared = require('./shared');
const b = require('./b');
console.log('a.js requiring b');
exports.message = 'Hello from a';
console.log('a.js finished');

b.js

console.log('b.js starting');
const shared = require('./shared');
const a = require('./a');
console.log('b.js requiring a');
exports.message = 'Hello from b';
console.log('b.js finished');

这样通过shared.js模块,减少了a.jsb.js之间的直接循环依赖。