MST

星途 面试题库

面试题:Node.js 中 CommonJS 规范下模块加载机制及循环依赖处理

阐述 Node.js 遵循 CommonJS 规范时模块的加载机制,尤其是当出现循环依赖时,Node.js 是如何处理的?请举例说明循环依赖场景并分析其处理过程。
23.9万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

Node.js 遵循 CommonJS 规范时模块的加载机制

  1. 首次加载:当 Node.js 遇到 require 语句加载模块时,它会首先检查该模块是否已经在缓存中。如果模块在缓存中,则直接返回缓存中的导出对象。
  2. 模块定位:若模块不在缓存中,Node.js 会根据 require 中的路径来定位模块。路径可以是核心模块(如 fshttp 等),直接加载;也可以是相对路径(如 ./module.js)或绝对路径,按路径查找;还可以是包名,在 node_modules 目录中查找。
  3. 模块编译:找到模块后,Node.js 将模块代码包装在一个函数中,该函数提供了 exportsrequiremodule 等局部变量。然后对包装后的代码进行编译执行。执行过程中,模块通过 exportsmodule.exports 来暴露其接口。

循环依赖处理

  1. 处理方式:当出现循环依赖时,Node.js 会在模块第一次被 require 时,就为其在缓存中创建一个空对象。然后继续加载依赖的模块。当依赖模块加载完成并填充其 exports 对象后,再回过头来完成最初模块的加载。
  2. 举例说明
    • 模块 A(a.js)
// a.js
console.log('a starting');
const b = require('./b');
console.log('a b:', b.value);
exports.value = 'a';
console.log('a finished');
- **模块 B(b.js)**:
// b.js
console.log('b starting');
const a = require('./a');
console.log('b a:', a.value);
exports.value = 'b';
console.log('b finished');
- **主模块(main.js)**:
// main.js
console.log('main starting');
const a = require('./a');
console.log('main a:', a.value);
console.log('main finished');
- **分析处理过程**:
    - 在 `main.js` 中 `require('./a')`,Node.js 开始加载 `a.js`。
    - `a.js` 开始执行,打印 `a starting`,然后 `require('./b')`,开始加载 `b.js`。
    - `b.js` 开始执行,打印 `b starting`,接着 `require('./a')`。此时 `a.js` 正在加载中,Node.js 从缓存中取出 `a.js` 对应的空对象返回给 `b.js`。
    - `b.js` 继续执行,打印 `b a: undefined`,因为此时 `a.js` 还未完成加载,`a.value` 未定义。然后 `b.js` 给 `exports.value` 赋值为 `b`,打印 `b finished`,`b.js` 加载完成。
    - 回到 `a.js`,继续执行,此时 `b.value` 已定义,打印 `a b: b`,然后给 `exports.value` 赋值为 `a`,打印 `a finished`,`a.js` 加载完成。
    - 回到 `main.js`,继续执行,打印 `main a: a`,然后打印 `main finished`。最终结果展示了 Node.js 对循环依赖的处理过程,虽然有循环依赖,但仍能正确加载并执行各模块。