1. 模块化发展简史

当我们在2005年前后写JavaScript时,全局变量的污染就像野草丛生的后院。随着前端工程复杂度的指数级增长,开发者们开始思考如何将代码拆分成可维护的单元。这种需求催生了最早的"模块化编程"概念,最终发展出了三种代表性解决方案:AMD、CommonJS和ES modules。

2. CommonJS:Node.js的默认选择

技术栈:Node.js v14+

2.1 基础用法

// mathUtils.js
const PI = 3.1415926

function circleArea(r) {
  return PI * r * r
}

// 标准导出语法
module.exports = {
  calcArea: circleArea
}

// index.js
const { calcArea } = require('./mathUtils')

console.log(`圆的面积:${calcArea(5)}`) // 输出:78.539815

2.2 动态加载特性

// 运行时动态决定加载模块
const isProduction = process.env.NODE_ENV === 'production'
const logger = isProduction 
  ? require('./simpleLogger') 
  : require('./detailLogger')

logger.log('系统启动完成')

优点:

  • 天然的同步加载机制
  • 明确的导出/导入语义
  • 成熟的工具链支持

注意事项:

  • 循环依赖可能导致意外结果
  • 浏览器环境需要打包工具转换
  • 默认进行缓存(可能需delete require.cache清理)

3. AMD:浏览器的异步解决方案

技术栈:RequireJS 2.3.6

3.1 基本模块定义

<!-- index.html -->
<script src="require.js" data-main="main"></script>

// main.js
requirejs.config({
  paths: {
    'jquery': 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min'
  }
})

require(['jquery', './mathModule'], function($, math) {
  $('#result').text(math.cube(3)) // 输出27
})

// mathModule.js
define(['jquery'], function($) {
  function cube(x) {
    return x * x * x
  }

  // 模块返回的值将作为导出对象
  return {
    cube: cube
  }
})

3.2 插件扩展机制

// 加载文本内容插件
require(['text!template.html'], function(tpl) {
  document.getElementById('container').innerHTML = tpl
})

最佳实践场景:

  • 遗留浏览器项目维护
  • 需要动态加载的仪表盘应用
  • 多入口SPA应用的按需加载

4. ES Modules:现代标准方案

技术栈:Chrome 90+ / Node.js 14+

4.1 基础实现示例

// geometry.mjs
const TAU = 2 * Math.PI

export function circleCircumference(r) {
  return TAU * r
}

// app.mjs
import { circleCircumference } from './geometry.mjs'

console.log(`周长:${circleCircumference(3)}`) // 18.84955592153876

4.2 动态加载模式

// 按需加载大型模块
document.getElementById('mapBtn').addEventListener('click', async () => {
  const mapModule = await import('./mapRenderer.js')
  mapModule.initMap()
})

4.3 复合用法示例

// logger.mjs
export default class Logger {
  constructor(name) {
    this.name = name
  }

  log(message) {
    console.log(`[${new Date().toISOString()}] ${this.name}: ${message}`)
  }
}

// 统一导出入口
export * from './network.js'
export { default as UserModel } from './user.js'

升级注意事项:

  • 需要显式声明.mjs扩展名(或配置type)
  • Node.js中需添加--experimental-modules标志(v14)
  • 浏览器环境需