面试官 : “ 说一下 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 需 .mjspackage.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(标准化、支持按需加载)。
相关推荐
贝格前端工场2 小时前
困在像素里:我的可视化大屏项目与前端价值觉醒
前端·three.js
indexsunny2 小时前
互联网大厂Java面试实战:核心技术与微服务架构解析
java·数据库·spring boot·缓存·微服务·面试·消息队列
float_六七2 小时前
用 `<section>` 而不是 `<div>的原因
前端
ChinaLzw2 小时前
解决uniapp web-view 跳转到mui开发的h5项目 返回被拦截报错的问题
前端·javascript·uni-app
程序员清风2 小时前
贝壳一面:Spring是怎么实现的?谈谈你的理解?
java·后端·面试
用户12039112947262 小时前
从零起步,用TypeScript写一个Todo App:踩坑与收获分享
前端·react.js·typescript
悟能不能悟2 小时前
Postman Pre-request Script 详细讲解与高级技巧
java·开发语言·前端
清 澜2 小时前
大模型扫盲式面试知识复习 (一)
人工智能·面试·大模型
上海物联网2 小时前
Prism Regions-自定义区域适配器实现开发者将任意 WPF 控件转换为可动态加载视图的区域容器
面试·wpf