面试题答案
一键面试文件查找阶段
- ES Modules:
- Node.js 遵循 ECMAScript 模块规范的文件查找规则。对于相对导入(如
import './module.js'
),会根据当前模块所在目录进行查找。如果导入路径以/
开头(在浏览器环境中有特殊含义,但在 Node.js 中通常不常见),则会从文件系统根目录开始查找。 - 对于裸模块导入(如
import 'lodash'
),Node.js 会在node_modules
目录中查找对应的包。它会从当前模块所在目录开始,逐步向上级目录查找node_modules
目录,直到找到匹配的包或者到达文件系统根目录。
- Node.js 遵循 ECMAScript 模块规范的文件查找规则。对于相对导入(如
- CommonJS:
- 同样对于相对路径(如
require('./module.js')
),基于当前模块所在目录查找。对于裸模块,也会在node_modules
目录中查找。它的查找逻辑也是从当前模块目录开始,向上级目录查找node_modules
目录,找到匹配的模块。不同之处在于,CommonJS 模块查找时,会优先查找.js
、.json
、.node
后缀的文件,若找到同名目录,会查找该目录下的package.json
文件中的main
字段指定的入口文件,若没有package.json
或者main
字段未指定,则查找该目录下的index.js
文件。
- 同样对于相对路径(如
解析阶段
- ES Modules:
- 解析过程遵循 ECMAScript 模块语法规则。Node.js 会解析
import
和export
语句,构建模块的依赖关系树。在解析时,导入和导出的绑定是静态分析的,即在编译阶段就确定,这有助于实现诸如 Tree - shaking 等优化。 - 对于动态导入(
import()
),会在运行时解析,并且返回一个 Promise。
- 解析过程遵循 ECMAScript 模块语法规则。Node.js 会解析
- CommonJS:
- 解析
require
语句,确定依赖模块的路径。CommonJS 模块的解析是动态的,require
可以在模块的任何位置使用,并且每次调用require
都会重新解析模块路径。它不像 ES Modules 那样进行静态分析,所以无法进行 Tree - shaking 等基于静态分析的优化。
- 解析
实例化阶段
- ES Modules:
- 在实例化阶段,Node.js 会为模块创建一个实例,包括为模块的顶层变量分配内存空间,但不会执行模块代码。模块的导入绑定会被创建,但实际的值尚未确定,只有当模块执行时才会确定。
- 模块之间的导入绑定是实时绑定的,即如果一个模块导出的值发生变化,导入该值的模块会立即看到变化。
- CommonJS:
- 当
require
一个模块时,会创建模块的实例。与 ES Modules 不同,CommonJS 模块实例化时,模块代码会立即执行,并且exports
对象会被填充。后续再次require
同一个模块时,会直接返回已缓存的实例,不会再次执行模块代码。
- 当
执行阶段
- ES Modules:
- 只有当模块被首次导入到应用程序中,并且所有依赖模块都已实例化后,模块代码才会执行。在执行过程中,会按照模块的代码逻辑,确定导入绑定的值,并执行模块内的其他语句。
- 由于 ES Modules 支持顶层
await
,在执行时,如果遇到顶层await
,模块的执行会暂停,直到 Promise 被解决。
- CommonJS:
- 如前所述,模块代码在实例化时就会执行。执行过程中,会将模块内的导出内容挂载到
exports
对象上,require
调用返回的就是这个exports
对象。
- 如前所述,模块代码在实例化时就会执行。执行过程中,会将模块内的导出内容挂载到
交互逻辑
- 从 ES Modules 导入 CommonJS:
- Node.js 会将 CommonJS 模块视为默认导出的 ES Module。即当从 ES Module 中导入 CommonJS 模块时,CommonJS 模块的
exports
对象会作为 ES Module 的默认导出。例如,如果有一个 CommonJS 模块commonjs.js
导出module.exports = { value: 42 }
,在 ES Module 中import cjs from './commonjs.js'
,cjs
就是{ value: 42 }
。
- Node.js 会将 CommonJS 模块视为默认导出的 ES Module。即当从 ES Module 中导入 CommonJS 模块时,CommonJS 模块的
- 从 CommonJS 导入 ES Modules:
- 在 Node.js 中,CommonJS 环境通过
import()
动态导入语法来支持导入 ES Modules。由于 CommonJS 是动态解析,而 ES Modules 导入绑定是静态的,所以这种导入返回一个 Promise,需要通过.then()
来处理导入结果。例如,在 CommonJS 模块中可以使用import('./esmodule.js').then((esm) => { /* 使用 esm */ })
。
- 在 Node.js 中,CommonJS 环境通过
Node.js 通过上述一系列机制实现了 ES Modules 与 CommonJS 的兼容性,在不同模块类型的处理上各有特点,同时又提供了交互的桥梁,使得开发者可以在项目中灵活使用不同的模块规范。