前端模块化(commonJS和ES Module)

一、为什么需要模块化?

在早期前端开发中,所有 JS 都写在一个文件里,或者用多个

  1. 全局变量污染:不同文件里的变量可能重名,覆盖导致 bug。

  2. 依赖管理混乱:多个文件加载顺序不对就会报错(比如先用 jQuery 后加载 jQuery 源码)。

  3. 维护困难:项目大了,功能代码难以分层,复用性差。

模块化就是为了解决这些问题,把代码拆分成 独立的模块(独立作用域、职责单一),按需引入使用。

二、模块化发展历程

  1. 立即执行函数(IIFE)阶段
    • 不是规范,只是一种 代码写法(非官方)

定义 moduleA.js

javascript 复制代码
// moduleA.js
var moduleA = (function () {
  var count = 0;
  function add() {
    return ++count;
  }
  return { add };
})();

在 HTML 引入

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>IIFE 模块示例</title>
</head>
<body>
  <div id="app"></div>

  <!-- 先引入 moduleA -->
  <script src="moduleA.js"></script>
  <!-- 再写业务代码 -->
  <script>
    console.log(moduleA.add()); // 1
    console.log(moduleA.add()); // 2
  </script>
</body>
</html>

优点:避免污染全局作用域。

缺点:依赖、模块间关系不好管理。


  1. CommonJS(Node.js 推广的规范)
    • 2009 年 Node.js 社区提出的规范,不是 ECMAScript 官方标准。
    • 使用 require 导入,module.exports 导出。
    • 使用函数实现。
    • 仅node环境支持。
    • 动态依赖。
    • 动态依赖是同步执行的。
javascript 复制代码
// math.js
function add(a, b) {
  return a + b;
}
module.exports = { add };

// main.js
const { add } = require('./math');
console.log(add(2, 3)); // 5

  1. AMD(Asynchronous Module Definition)
    • define:定义模块(依赖哪些模块,返回什么对象)。
    • require:加载模块并执行。
    • 需要 引入 RequireJS 才能让 AMD 规范跑起来,否则浏览器不认识 define / require。
    • 特点:异步加载模块,适合浏览器。
    • 写法繁琐,社区用过一阵子(社区规范,非官方),但现在基本淘汰。

定义模块 moduleA.js

javascript 复制代码
// moduleA.js
define(['jquery'], function ($) {
  return {
    show: () => $('#app').text('hello AMD')
  }
});

在 HTML 里引入 RequireJS 和模块

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>AMD 示例</title>
  <!-- 引入 RequireJS,data-main 表示入口文件 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js" 
          data-main="main"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

编写入口文件 main.js

javascript 复制代码
// main.js
require(['./moduleA'], function (moduleA) {
  moduleA.show();
});

  1. CMD(Common Module Definition)
    • 特点:依赖就近、按需加载。
    • 由国内的 SeaJS 团队提出。
    • 社区规范,非官方。
    • define:定义一个 CMD 模块。
    • require:在函数内部按需加载依赖(CMD 特点:依赖就近)。
    • exports / module.exports:对外暴露 API。
    • seajs.use:入口调用,类似 AMD 的 require([...], callback)。

.定义模块 moduleA.js

javascript 复制代码
// moduleA.js
define(function(require, exports, module) {
  var $ = require('jquery');
  exports.show = () => $('#app').text('hello CMD');
});

在 HTML 里引入 SeaJS

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>CMD 示例</title>
  <!-- 引入 SeaJS -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/seajs/3.0.0/sea.js"></script>
</head>
<body>
  <div id="app"></div>

  <script>
    // 使用 seajs.use 加载入口模块
    seajs.use('./moduleA.js', function(moduleA) {
      moduleA.show();
    });
  </script>
</body>
</html>

区别对比:

  • AMD (RequireJS):依赖前置,提前声明。

  • CMD (SeaJS):依赖就近,用到时才 require。


  1. ES Module(ESM,现代前端的主流方案)
    • 静态依赖分析(编译时就能确定依赖关系),浏览器和 Node.js 都原生支持。
    • ECMAScript 官方标准,是现代前端的 唯一官方模块化方案。
    • 动态依赖是异步的。
    • 符号绑定。
javascript 复制代码
// math.js
export function add(a, b) {
  return a + b;
}

// main.js
import { add } from './math.js';
console.log(add(2, 3)); // 5

关于符号绑定:

在 ES Module 里,import 导入的不是值的拷贝,而是和原始模块变量的"绑定"(引用关系)。

  • 这个绑定是 只读的(不能在 import 模块里重新赋值)。

  • 但是如果原始模块里变量的值发生变化,import 进来的地方能感知到更新。

javascript 复制代码
//a.js
export let count = 0;

export function add() {
  count++;
}

//b.js
import { count, add } from './a.js';

console.log(count); // 0
add();
console.log(count); // 1 ✅ 自动更新,因为是绑定

在这里,count 在 a.js 里变了,b.js 里看到的 count 也跟着变。

三、模块化的好处

  1. 作用域隔离:避免全局污染。

  2. 按需加载:提升性能。

  3. 依赖清晰:结构更清楚,便于维护。

  4. 复用性强:不同项目可复用模块。

四、现代前端中的模块化工具

即使有了 ES Module,实际项目中还是需要 打包工具:

  • Webpack:支持 CommonJS、ESM、AMD,适合大型项目。

  • Rollup:更适合库的打包,Tree-shaking 优秀。

  • Vite / ESBuild:基于 ESM 的新一代构建工具,速度快。

相关推荐
Virgil1393 小时前
【YOLO学习笔记】数据增强mosaic、Mixup、透视放射变换
笔记·学习·yolo
小old弟4 小时前
前端异常隔离方案:Proxy代理、Web Workers和iframe
前端
脑子慢且灵4 小时前
【Web前端】JS+DOM来实现乌龟追兔子小游戏
java·开发语言·前端·js·dom
TimelessHaze4 小时前
前端面试必问:深浅拷贝从基础到手写,一篇讲透
前端·trae
CaptainDrake4 小时前
React 中 key 的作用
前端·javascript·react.js
全栈技术负责人4 小时前
移动端富文本markdown中表格滚动与页面滚动的冲突处理:Touch 事件 + 鼠标滚轮精确控制方案
前端·javascript·计算机外设
前端拿破轮4 小时前
从零到一开发一个Chrome插件(二)
前端·面试·github
珍宝商店4 小时前
Vue.js 中深度选择器的区别与应用指南
前端·javascript·vue.js
天蓝色的鱼鱼4 小时前
Next.js 预渲染完全指南:SSG vs SSR,看完秒懂!
前端·next.js