一文读懂:CommonJS 和 ES Module 的本质区别

面试官:你能说说 CommonJS 和 ES Module 的区别吗?

我:......(脑子里只剩下 requireimport

说实话,这个问题你一定见过 ,而且99% 的前端都背过标准答案

但真要往深了问一句:

  1. 为什么 ESM 可以 Tree Shaking?CommonJS 不行
  2. 为什么 ESM 的 import 是"只读的"?

很多人,当场就开始"CPU 过载"。

于是我决定直接把底层逻辑捋清楚,以下就是我对 CommonJS 和 ES Module 一次系统性深挖的记录

一、什么是 CommonJS?它解决了什么问题?

1. CommonJS 的诞生背景

在早期 JavaScript 只有浏览器环境时,是没有模块系统的

  • 全局变量污染
  • 文件之间依赖混乱
  • 无法复用代码

于是 Node.js 社区 提出了一套解决方案:CommonJS(CMJ)

👉 注意 :CommonJS 是社区标准,不是官方语言层面的规范。

CommonJS 的核心特征

  • ✅ 社区标准
  • ✅ 使用函数实现(require
  • ✅ 仅 Node 环境支持
  • ✅ 动态依赖,同步执行

2. CommonJS 为什么叫"动态依赖"?

来看一段最典型的代码:

js 复制代码
const moduleName = './a.js';
const a = require(moduleName);

这里的依赖路径,是不是运行时才能确定?这就是动态依赖;

CommonJS 的依赖关系,必须等代码执行时才能知道


3. require 到底做了什么?(核心原理)

你在 Node 中写的:

js 复制代码
const a = require('./a.js');

但如果我追问一句:require 加载的模块代码,是"直接执行"的吗? 模块里的 this、exports、module.exports 到底从哪来的?

答案其实藏在 Node.js 对模块的一层"函数包装"里:

js 复制代码
function require(path) {
   const cache = {}
  // 1. 如果模块已经加载过,直接返回缓存
  if (cache[path]) {
    return cache[path].exports;
  }

  // 2. 创建模块对象
  const module = {
    id:path
    exports: {}
  };

  // 3. 执行模块代码(用函数包一层)
  function _run(exports, require, module, __filename, __dirname) {
    // 模块源码在这里执行
  }

  _run.call(
    module.exports,
    module.exports,
    require,
    module,
    __filename,
    __dirname
  );

  // 4. 缓存并返回结果
  cache[modulePath] = module;
  return module.exports;
}

假设你有一个文件 a.js,那么文件中的内容会放到上面的_run函数中执行

我们拆开来看:

名称 实际指向
this module.exports
exports module.exports
module.exports module.exports

在模块初始化阶段,这三个引用的是同一个对象。

所以以下判断永远成立:

js 复制代码
console.log(arguments); // [exports, require ,module, __filename, __dirname]
console.log(this); // {}
console.log(this === exports); // true
console.log(exports === module.exports); // true

重点来了

  • require 是一个普通函数
  • module.exports 是一个普通对象
  • 模块执行是同步的
  • 导出的值是一次性的值拷贝

二、ES Module:语言层面的模块系统

如果说 CommonJS 是"工具方案",那么 ES Module(ESM)就是 JavaScript 官方给出的答案

ES Module 的核心关键

  • ✅ 官方标准(ECMAScript)
  • ✅ 使用新语法(import / export
  • 所有环境支持(浏览器 / Node / Deno)
  • ✅ 同时支持静态依赖 & 动态依赖
  • 符号绑定

1. 什么是「静态依赖」?

js 复制代码
import { a } from './a.js';

这行代码有两个关键点:

  1. import只能写在顶层
  2. 依赖路径在代码运行前就确定

👉 这意味着什么?

  • 构建工具在编译阶段就能分析依赖
  • 支持Tree Shaking
  • 可以做代码分割、预加载

这也是为什么 ESM 更适合前端工程化


2. ESM 也支持动态依赖,但它是异步的

js 复制代码
import('./a.js').then(module => {
  console.log(module.a);
});

和 CommonJS 最大的不同点:

模块系统 动态依赖
CommonJS 同步
ES Module 异步

3. 符号绑定:ESM 最容易被忽略

这是 ESM 和 CommonJS 的本质区别

看一段代码

js 复制代码
// a.js
export var a = 1;
export function changeA() {
  a = 2;
}
js 复制代码
// index.js
import { a, changeA } from './a.js';
console.log(a); // 1
changeA();
console.log(a); // 2

这里为什么 a 会跟着变化?

真相就是 import 不是赋值,而是"引用同一个符号"

在 ESM 中: 导入的不是值,而是对导出符号的实时绑定

可以理解为:

  • a 在模块内部是一个变量
  • 所有 import 的地方,都指向同一个 a
  • 修改它,所有地方同步变化

这就是「符号绑定(Live Binding)」。


对比 CommonJS(非常关键)

js 复制代码
// a.js
var n = 1;
function changeN() {
  n = 2;
}
module.exports = {
  n,
  changeN
}

// b.js
const { n, changeN } = require('./a.js');
console.log(n); // 1
changeN();
console.log(n); // 1

这里的 n

  • 是一次值拷贝
  • 后续模块内部怎么改,外面都不会同步

4. 再看下 下面几个问题

(1) export 和 export default 的区别

  • export:具名导出,可多个
  • export default:默认导出,只能一个
  • 默认导出本质是 { default: xxx }

(2) 下面代码导出了什么?

js 复制代码
exports.a = 'a';
module.exports.b = 'b';
this.c = 'c';
module.exports = {
  d: 'd'
};

结果:

js 复制代码
{ d: 'd' }

(3)下面代码导出了什么?

js 复制代码
exports.a = 1;  
exports = { b: 2 };

结果:

js 复制代码
{ a: 1 }

原因是:

  • exports 只是 module.exports 的一个引用
  • 当你重新给exports赋值时,只是断开了引用关系module.exports 并没有变
  • 等价于 let exports = module.exports; exports = {}; 只是改了局部变量
相关推荐
zhougl9961 小时前
Composition API 和 Options API
前端·javascript·vue.js
wuhen_n1 小时前
虚拟DOM:VNode的设计与创建
前端·javascript·vue.js
归叶再无青1 小时前
web服务安装部署、性能升级等(Apache、Nginx)
运维·前端·nginx·云原生·apache·bash
你怎么知道我是队长1 小时前
前端学习---HTML---表单
前端·学习·html
阿巴资源站1 小时前
uniapp加水印
java·前端·uni-app
Ai老司机1 小时前
Chrome浏览器驱动(ChromeDriver)官方下载 - 全平台高速镜像站 | chromedrive.cn
前端·chrome
Pitiless-invader1 小时前
MySQL 相关知识及面试问题汇总
面试·职场和发展
西门吹-禅1 小时前
[sap fiori rap cds--behavior]
前端·fiori·rap·cds
梵得儿SHI1 小时前
Vue3 生态工具实战宝典:UI 组件库 + 表单验证全解析(Element Plus/Ant Design Vue/VeeValidate)
前端·vue.js·ui·elementplus·vue性能优化·antdesignvue·表单验证方案