面试官 : “ 说一下 ES6 模块与 CommonJS 模块的差异 ? ”

先甩结论 :

它们有三个重大差异。

  • CommonJS 模块输出的是一个 值的拷贝 ,ES6 模块输出的是 值的引用。
  • CommonJS 模块是 运行时 加载,ES6 模块是 编译时 输出接口。
  • CommonJS 模块的require()是 同步 加载模块,ES6 模块的import命令是 异步 加载,有一个独立的模块依赖的解析阶段。

一、核心定位与运行环境

  • CommonJS(CJS) :Node.js 早期的模块化规范,为服务端设计(同步加载),也被 Browserify 等工具适配到浏览器端。
  • ES6 模块(ESM) :ES6 官方定义的模块化规范,适配浏览器 + 服务端,是前端模块化的标准方案,Node.js v14.13+ 也全面支持。

二、差异对比表

对比维度 CommonJS 模块 ES6 模块
加载时机 运行时加载(动态):模块代码在 require 执行时才加载、执行 编译时加载(静态):编译阶段就确定模块依赖关系,提前解析导入导出
导入导出语法 导出:module.exports / exports;导入:require() 导出:export / export default;导入:import / import()
值的绑定方式 赋值传递:导入的是值的拷贝,模块内部值变化不影响导入值 引用传递:导入的是值的实时引用,模块内部值变化会同步到导入端
执行机制 模块代码同步执行,加载完成后才继续执行后续代码 模块代码异步执行(浏览器端),Node.js 中可同步 / 异步
顶层 this 指向 指向当前模块的 module.exports 对象 指向 undefined
是否支持按需导入 不支持(静态分析无法识别 require 动态路径) 支持(import() 动态导入、import { a } from 'xxx' 按需导入)
循环依赖处理 加载时生成未完成的模块对象,后续补全(可能拿到空对象) 编译时确定依赖关系,通过 "暂时性死区" 处理,更稳定
文件后缀 默认 .js,Node.js 中可省略后缀 浏览器端需 .mjs 或配置 type="module";Node.js 需 .mjs 或 package.json 设 "type": "module"

三、关键差异详解(附代码示例)

1. 语法差异(最直观)

CommonJS 示例:

javascript 复制代码
// 导出(module.js)
// 方式1:整体导出
module.exports = {
  name: 'CommonJS',
  sayHi: () => console.log('hi')
};
// 方式2:单个导出
exports.age = 18; // 等价于 module.exports.age = 18

// 导入(index.js)
const mod = require('./module.js');
console.log(mod.name); // CommonJS
console.log(mod.age); // 18

ES6 模块示例:

javascript 复制代码
// 导出(module.js)
// 方式1:命名导出
export const name = 'ES6 Module';
export const age = 18;
// 方式2:默认导出
export default {
  sayHi: () => console.log('hi')
};

// 导入(index.js)
// 命名导入 + 默认导入
import { name, age } from './module.js';
import defaultMod from './module.js';
console.log(name); // ES6 Module
defaultMod.sayHi(); // hi

2. 值绑定方式差异(核心区别)

CommonJS(值拷贝) :

javascript 复制代码
// module.js(CJS)
let count = 0;
setTimeout(() => count = 10, 1000);
module.exports = { count };

// index.js(CJS)
const { count } = require('./module.js');
console.log(count); // 0(初始值拷贝)
setTimeout(() => console.log(count), 1000); // 仍为 0(拷贝值不更新)

ES6 模块(引用传递) :

javascript 复制代码
// module.js(ESM)
export let count = 0;
setTimeout(() => count = 10, 1000);

// index.js(ESM)
import { count } from './module.js';
console.log(count); // 0(初始值)
setTimeout(() => console.log(count), 1000); // 10(实时引用更新)

3. 加载时机差异

  • CommonJS:require('./a.js') 执行时才会读取 a.js 文件、执行代码、生成导出对象,支持动态路径(如 require('./' + fileName))。
  • ES6 模块:import 必须写在顶层(不能在 if 里),编译阶段就确定所有导入,不支持动态路径(需用 import('./a.js') 动态导入,返回 Promise)。

四、实战注意事项

  1. Node.js 中混用两种模块:

    • 后缀 .cjs 强制为 CommonJS,.mjs 强制为 ES6 模块;
    • package.json 中设置 "type": "module",则 .js 文件默认是 ES6 模块;设为 "commonjs" 则默认是 CJS。
  2. 浏览器端使用 ES6 模块:

    xml 复制代码
    <!-- 必须加 type="module" -->
    <script type="module" src="./index.js"></script>
  3. 循环依赖处理:ES6 模块的循环依赖更可靠,CommonJS 需注意模块加载顺序,避免拿到空对象。

总结

  1. 核心区别:CJS 是运行时动态加载(值拷贝),ESM 是编译时静态加载(值引用);
  2. 语法区别 :CJS 用 require/module.exports,ESM 用 import/export;
  3. 使用场景:Node.js 老项目多用 CJS,现代前端 / 新版 Node.js 优先用 ESM(标准化、支持按需加载)。
相关推荐
GoAI2 小时前
DeepSeek Harness Memory (一)主流记忆插件总结
后端·算法·面试
卸任3 小时前
Vibe coding工具使用心得
前端·ai编程
brien93 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance4 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M4 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写4 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李4 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易4 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding4 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马4 小时前
前沿视界
前端