Node.js 模块系统:CommonJS vs ES Modules

Node.js 模块系统:CommonJS vs ES Modules

理解两种模块系统的区别和使用场景

概述

Node.js 支持两种模块系统:

特性 CommonJS ES Modules
关键字 require / module.exports import / export
加载方式 运行时同步加载 编译时静态加载
文件扩展名 .js .mjs.cjs
Node.js 支持 默认支持 v13.2+ 稳定支持

CommonJS (CJS)

导出

javascript 复制代码
// math.js

// 方式1:导出单个值
module.exports = function add(a, b) {
  return a + b;
};

// 方式2:导出多个属性
module.exports.add = function(a, b) { return a + b; };
module.exports.subtract = function(a, b) { return a - b; };

// 方式3:使用 exports(注意:不能直接赋值)
exports.PI = 3.14159;
exports.multiply = function(a, b) { return a * b; };

导入

javascript 复制代码
// app.js

// 导入整个模块
const math = require('./math');
console.log(math.add(1, 2));

// 解构导入
const { add, PI } = require('./math');
console.log(PI);

// 导入内置模块
const fs = require('fs');
const path = require('path');

// 导入第三方模块
const express = require('express');

package.json 配置

json 复制代码
{
  "type": "commonjs"  // 默认,可省略
}

ES Modules (ESM)

导出

javascript 复制代码
// math.js 或 math.mjs

// 命名导出
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

// 默认导出
export default function(a, b) {
  return a + b;
}

// 或者集中导出
const PI = 3.14159;
const add = (a, b) => a + b;
export { PI, add };

导入

javascript 复制代码
// app.js 或 app.mjs

// 默认导入
import add from './math.js';

// 命名导入
import { add, subtract, PI } from './math.js';

// 重命名导入
import { add as sum } from './math.js';

// 导入所有
import * as math from './math.js';

// 副作用导入(只执行,不导入)
import './polyfill.js';

package.json 配置

json 复制代码
{
  "type": "module"
}

或使用 .mjs 扩展名(自动启用 ESM)


主要区别

1. 加载时机

javascript 复制代码
// CommonJS - 运行时加载
const math = require('./math');  // 同步执行

// ES Modules - 编译时加载
import { add } from './math';    // 静态分析

2. 导出方式

javascript 复制代码
// CommonJS - 值拷贝
let count = 0;
module.exports = { count };
count = 1;  // 导出值不变,仍是 0

// ES Modules - 动态引用
export let count = 0;
count = 1;  // 导出值同步更新为 1

3. this 指向

javascript 复制代码
// CommonJS - this 指向 module.exports
console.log(this === module.exports);  // true

// ES Modules - this 是 undefined
console.log(this);  // undefined

实战示例

CommonJS 项目结构

复制代码
project/
├── package.json (默认 commonjs)
├── utils/
│   └── math.js
└── app.js
javascript 复制代码
// utils/math.js
module.exports.add = (a, b) => a + b;

// app.js
const { add } = require('./utils/math');
console.log(add(1, 2));

ES Modules 项目结构

复制代码
project/
├── package.json ("type": "module")
├── utils/
│   └── math.js
└── app.js
javascript 复制代码
// utils/math.js
export const add = (a, b) => a + b;

// app.js
import { add } from './utils/math.js';
console.log(add(1, 2));

混用技巧

在 CJS 中使用 ESM

javascript 复制代码
// 需要 dynamic import
async function loadModule() {
  const { add } = await import('./math.mjs');
  return add(1, 2);
}

在 ESM 中使用 CJS

javascript 复制代码
// 可以直接导入
import express from 'express';
import { readFile } from 'fs';

选择建议

使用 CommonJS 当:

  • 需要动态加载模块
  • 兼容旧项目或工具
  • 编写简单的脚本工具

使用 ES Modules 当:

  • 开始新项目
  • 使用现代前端框架(React、Vue)
  • 需要_tree-shaking_ 优化
  • 编写可同时在浏览器和 Node.js 运行的代码

快速参考

javascript 复制代码
// CommonJS
module.exports = { value: 1 };
const { value } = require('./file');

// ES Modules
export const value = 1;
import { value } from './file.js';

总结

  • CommonJS:Node.js 传统方式,成熟稳定
  • ES Modules:现代标准,未来趋势
相关推荐
米丘20 小时前
从 HTTP 到 WebSocket:深入 Vite HMR 的网络层原理
http·node.js·vite
米丘20 小时前
Node.js 事件循环
前端·javascript·node.js
Kel20 小时前
深入 Ink 源码:当 React 遇见终端 —— Custom Reconciler 全链路剖析
react.js·架构·node.js
全马必破三1 天前
Vue3+Node.js 实现AI流式输出全解析
前端·javascript·node.js
吴声子夜歌1 天前
Node.js——util工具模块
node.js
笑笑先生1 天前
从接口搬运工到研发控制平面,BFF 到底在解决什么?
前端·架构·node.js
www_stdio1 天前
🚀 从 Event Loop 到 AI Agent:我的 Node.js 全栈进阶之路
前端·node.js·nestjs
吴声子夜歌1 天前
Node.js——fs文件系统模块
node.js
吴声子夜歌1 天前
Node.js——全局对象和模块
node.js
fxshy1 天前
前端直连模型 vs 完整 MCP:大模型驱动地图的原理与实践(技术栈Vue + Cesium + Node.js + WebSocket + MCP)
前端·vue.js·node.js·cesium·mcp