面试:CommonJs和ESM的区别

1、总结

简单来说:

  • CommonJS:主要用于 Node.js 服务端,设计理念是动态/运行时加载。语法简单。
  • ESM:JavaScript 语言标准,旨在统一前后端的模块化方案。设计理念是静态/编译时分析。功能更强大。

2、核心区别总结表

在这里插入图片描述

3、区别详解

3.1 语法与加载时机(动态 vs 静态)

CommonJS (动态)

// 导入
const { a, b } = require('./moduleA');
const path = require('path'); // 内置模块
// 导出
module.exports = { a, b };
exports.a = a; // 注意:不能直接对 exports 赋值
  • require() 是一个函数调用,在代码运行时才执行。
  • 这意味着模块路径可以是动态的,例如 require(condition ? ‘./A’ : ‘./B’)。

ESM (静态)

// 导入
import { a, b } from './moduleA.js';
import * as mod from './moduleA.js';
import theDefault from './moduleA.js'; // 默认导入
// 导出
export { a, b };
export default someValue;
  • import 关键字必须在模块顶层,在代码执行前(编译时)就被 JavaScript 引擎解析。
  • 这允许进行“静态分析”,比如 Tree Shaking(消除无用代码),因为打包工具在构建时就能确定模块的依赖关系。

3.2 值的引用 vs 值的拷贝(最关键的区别之一)

CommonJS (值的拷贝)

// lib.js
let counter = 1;
function increment() {
  counter++;
}
module.exports = { counter, increment };

// main.js
const { counter, increment } = require('./lib');

console.log(counter); // 1
increment();
console.log(counter); // **1** (没有改变!)

解释: main.js 导入的 counter 是 lib.js 中 counter 的一个副本。调用 increment() 修改的是原模块里的 counter,但不会影响 main.js 中的副本。

ESM (值的引用)

// lib.js
export let counter = 1;
export function increment() {
  counter++;
}

// main.js
import { counter, increment } from './lib.js';

console.log(counter); // 1
increment();
console.log(counter); // **2** (改变了!)

解释: main.js 导入的 counter 是对 lib.js 中 counter 变量的一个只读引用。当原模块的值发生变化时,所有引用该绑定的模块都会看到最新的值。

3.3 循环依赖的处理

循环依赖(A 导入 B,B 又导入 A)能很好地体现两种系统的差异。

CommonJS (可能得到不完整模块)

// a.js
exports.done = false;
const b = require('./b.js');
console.log('在 a.js 中,b.done = %j', b.done); // 在 a.js 中,b.done = true
exports.done = true;
console.log('a.js 执行完毕');

// b.js
exports.done = false;
const a = require('./a.js');
console.log('在 b.js 中,a.done = %j', a.done); // 在 b.js 中,a.done = false
exports.done = true;
console.log('b.js 执行完毕');

// main.js
const a = require('./a.js');
const b = require('./b.js');

结果:由于 require() 是动态的,并且会缓存已执行模块的 module.exports 对象,当 b.js 在执行中途去 require(‘a.js’) 时,它拿到的是 a.js 当前已经执行到的部分(即 {done: false}),而不是最终值。

ESM (引用机制更健壮)
ESM 的加载器会先构建一个模块依赖图,将所有 import 和 export 连接起来。然后在执行代码时,这些绑定是“活”的。

如果一个模块在循环依赖中尚未执行完毕,另一个模块引入它时,虽然能看到它已导出的绑定,但其值可能只是初始值(如 undefined)。由于是动态映射,当原模块执行完毕后,所有引用处的值都会更新。这通常能带来更可预测的行为。

3.4 环境与现状

  • CommonJS:是 Node.js 诞生之初的默认模块系统。在服务端领域根深蒂固。NPM 生态中大多数包最初都是 CommonJS 格式。
  • ESM:是现代 JavaScript 的标准。所有现代浏览器都原生支持。从 Node.js v12 开始,在提供了 .mjs 扩展名或 package.json 中设置 “type”: “module” 后,可以原生支持 ESM。

在 Node.js 中互操作:

  • 在 ESM 模块中,可以使用 import() 动态导入 CommonJS 模块。
  • 在 CommonJS 模块中,不能使用 require() 导入 ESM 模块。

4、总结和建议

在这里插入图片描述
现代开发建议:
对于新项目,应优先使用 ESM。它是语言的未来,并且其静态特性为构建工具优化提供了巨大空间。在 Node.js 环境中,只需在 package.json 中设置 “type”: “module” 即可。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳与星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值