模块化开发:当CommonJS遇见ES Module

欢迎使用我的小程序👇👇👇👇


一、为什么我们需要模块化?

想象一下,你正在建造一座乐高城堡。如果所有的积木都混在一个大袋子里,每次要找特定零件都得翻遍整个袋子,这会是多么低效!早期的JavaScript开发就是这样------所有代码都写在一个或几个文件中,导致:

  • 变量污染全局作用域
  • 难以维护和调试
  • 代码复用困难
  • 依赖关系混乱

模块化就像把乐高积木按颜色、形状分类放在不同的盒子里,让搭建变得更有序、更高效。

二、CommonJS:Node.js的"老将"

基本概念

CommonJS是Node.js默认的模块系统,它采用同步加载的方式,非常适合服务器端环境。

核心语法

javascript 复制代码
// 导出模块 - math.js
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// 方式1:导出单个对象
module.exports = { add, multiply };

// 方式2:逐个添加属性
exports.add = add;
exports.multiply = multiply;

// 导入模块 - app.js
const math = require('./math.js');
console.log(math.add(2, 3)); // 5

特点解析

同步加载:像在图书馆借书,必须等上一本书拿到手,才能借下一本

javascript 复制代码
// 模块会立即执行
const fs = require('fs'); // 阻塞执行,直到fs模块完全加载
const data = fs.readFileSync('file.txt'); // 继续阻塞

动态导入:你可以在条件语句中导入模块

javascript 复制代码
if (userNeedsAdvancedFeature) {
    const advancedModule = require('./advanced');
    // 使用模块
}

适用场景

  • Node.js后端开发
  • 构建工具(如Webpack、Browserify转换后可在浏览器使用)

三、ES Module:现代JavaScript的"新星"

基本概念

ES Module是ECMAScript 2015(ES6)引入的官方模块标准,采用异步加载,是现代前端开发的首选。

核心语法

javascript 复制代码
// 导出模块 - math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// 默认导出(每个模块只能有一个)
const defaultFunction = () => console.log('默认导出');
export default defaultFunction;

// 导入模块 - app.js
import { add, multiply } from './math.js';
import myDefault from './math.js'; // 导入默认导出

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

// 整体导入
import * as math from './math.js';

特点解析

静态分析:像餐厅点餐,先看完整菜单再决定点什么

javascript 复制代码
// 导入声明必须在顶层,不能在条件语句中
import { featureA } from './moduleA'; // ✓ 正确

if (condition) {
    import { featureB } from './moduleB'; // ✗ 错误!语法不允许
}

// 但可以使用动态导入函数
if (condition) {
    import('./moduleB').then(module => {
        // 使用module.featureB
    });
}

异步加载:多个模块可以并行加载

javascript 复制代码
// 模块1和模块2可以同时加载
import { utils } from './utils.js';
import { api } from './api.js';

// 动态导入返回Promise
const loadModule = async () => {
    const module = await import('./dynamic-module.js');
    module.doSomething();
};

实时绑定:导入的是值的引用,而不是副本

javascript 复制代码
// counter.js
export let count = 0;
export const increment = () => { count++; };

// app.js
import { count, increment } from './counter.js';

console.log(count); // 0
increment();
console.log(count); // 1(值被更新了!)

四、CommonJS vs ES Module:直接对比

特性 CommonJS ES Module
语法 require() / module.exports import / export
加载时机 运行时加载 编译时静态解析
加载方式 同步 异步
环境 主要为Node.js 浏览器和Node.js
值类型 值的拷贝 值的引用(实时绑定)
条件导入 支持 静态导入不支持,动态导入支持
循环依赖 支持但可能有问题 支持且更可靠
严格模式 默认非严格模式 默认严格模式

循环依赖示例对比

CommonJS的问题

javascript 复制代码
// a.js
exports.loaded = false;
const b = require('./b.js');
console.log('在a中,b是', b);
exports.loaded = true;

// b.js
exports.loaded = false;
const a = require('./a.js'); // 此时a还没有完全加载
console.log('在b中,a是', a); // a.loaded为false
exports.loaded = true;

ES Module的处理

javascript 复制代码
// a.js
import { bLoaded } from './b.js';
export let aLoaded = false;
aLoaded = true;

// b.js
import { aLoaded } from './a.js';
export let bLoaded = false;
bLoaded = true;
// 可以正常工作,但需要注意初始化顺序

五、在现代项目中如何使用?

Node.js中的使用

Node.js从v13.2.0开始稳定支持ES Module:

  1. 使用.mjs扩展名

    javascript 复制代码
    // module.mjs
    export const hello = () => "Hello ES Module!";
    
    // app.mjs
    import { hello } from './module.mjs';
  2. package.json中设置"type": "module"

    json 复制代码
    {
      "name": "my-app",
      "type": "module",
      "scripts": {
        "start": "node app.js"
      }
    }

浏览器中的使用

html 复制代码
<!-- 直接使用ES Module -->
<script type="module">
  import { createApp } from './app.js';
  createApp();
</script>

<!-- 支持相对和绝对路径 -->
<script type="module" src="./modules/main.js"></script>

互操作性

在ES Module中导入CommonJS模块:

javascript 复制代码
// 可以导入CommonJS模块
import commonJSModule from './commonjs-module.cjs';
import { namedExport } from './commonjs-module.cjs'; // 如果CommonJS模块有提供

// 在package.json中指定不同扩展名的处理方式

六、实践建议

  1. 新项目:优先使用ES Module,它是未来的标准

  2. Node.js项目

    • 新项目:使用ES Module(设置"type": "module"
    • 现有项目:逐步迁移或维持CommonJS
  3. 浏览器项目:使用ES Module,配合构建工具(如Webpack、Vite)

  4. 库/包开发 :考虑双模式支持

    json 复制代码
    // package.json
    {
      "name": "my-library",
      "main": "./dist/commonjs/index.js",
      "module": "./dist/esm/index.js",
      "exports": {
        "require": "./dist/commonjs/index.js",
        "import": "./dist/esm/index.js"
      }
    }

七、常见问题解答

Q:我应该学习哪一个? A:两者都要了解!ES Module是未来,但很多现有项目使用CommonJS。

Q:可以在同一个文件中混用吗? A:尽量避免,但在Node.js中可以通过动态导入互相调用。

Q:哪个性能更好? A:ES Module的静态特性允许更好的优化和摇树(tree-shaking)。

总结

模块化让JavaScript从"玩具语言"成长为"工程语言"。CommonJS像是一位经验丰富的老兵,在Node.js生态中建立了坚实基础;ES Module则像是一位充满活力的新星,代表着JavaScript的未来方向。

记住一个简单的选择策略:

  • 前端开发 → ES Module
  • Node.js新项目 → ES Module
  • Node.js旧项目维护 → CommonJS
  • 通用库开发 → 考虑双模式支持

无论选择哪种方式,模块化的核心思想都是一致的:关注点分离、高内聚低耦合、代码复用。掌握了这些概念,你就掌握了现代JavaScript开发的钥匙。

模块化不是目的,而是手段。真正的目标是编写可维护、可扩展、高质量的代码。

相关推荐
浩星5 小时前
css实现类似element官网的磨砂屏幕效果
前端·javascript·css
一只小风华~5 小时前
Vue.js 核心知识点全面解析
前端·javascript·vue.js
2022.11.7始学前端5 小时前
n8n第七节 只提醒重要的待办
前端·javascript·ui·n8n
SakuraOnTheWay5 小时前
React Grab实践 | 记一次与Cursor的有趣对话
前端·cursor
阿星AI工作室5 小时前
gemini3手势互动圣诞树保姆级教程来了!附提示词
前端·人工智能
徐小夕5 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
xhxxx5 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder5 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端
fxshy5 小时前
Cursor 前端Global Cursor Rules
前端·cursor
红彤彤5 小时前
前端接入sse(EventSource)(@fortaine/fetch-event-source)
前端