Skip to content
On this page

JavaScript 模块系统

模块系统是现代JavaScript开发的核心特性,它允许我们将代码分割成可重用的块,提高代码的可维护性和可组织性。

模块基础概念

模块是一个包含JavaScript代码的文件,它有自己的作用域,不会污染全局作用域。模块可以导出值、函数、类等,也可以导入其他模块导出的内容。

模块的特性

javascript
// 模块默认在严格模式下运行
// 在模块中,顶层的this是undefined,而不是window

// 模块只执行一次,结果会被缓存
// 无论导入多少次,模块只会被初始化一次

基本导出 (Named Exports)

javascript
// math.js - 命名导出
export const PI = 3.14159;

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

export function multiply(a, b) {
  return a * b;
}

// 也可以在末尾导出
function subtract(a, b) {
  return a - b;
}

function divide(a, b) {
  if (b === 0) throw new Error('除数不能为零');
  return a / b;
}

export { subtract, divide };

基本导入

javascript
// main.js - 导入命名导出
import { PI, add, multiply } from './math.js';

console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20

重命名导入/导出

javascript
// geometry.js
export const CIRCLE = 'circle';
export const SQUARE = 'square';

// main.js - 重命名导入
import { CIRCLE as CircleType, SQUARE as SquareType } from './geometry.js';

console.log(CircleType); // 'circle'
console.log(SquareType); // 'square'

// 重命名导出
export { CIRCLE as ROUND_SHAPE } from './geometry.js';

默认导出 (Default Export)

javascript
// calculator.js - 默认导出
class Calculator {
  add(a, b) {
    return a + b;
  }
  
  subtract(a, b) {
    return a - b;
  }
  
  multiply(a, b) {
    return a * b;
  }
  
  divide(a, b) {
    if (b === 0) throw new Error('除数不能为零');
    return a / b;
  }
}

export default Calculator;

// 或者默认导出函数
export default function calculate(operation, a, b) {
  switch (operation) {
    case 'add': return a + b;
    case 'subtract': return a - b;
    case 'multiply': return a * b;
    case 'divide': return a / b;
    default: throw new Error('未知操作');
  }
}

默认导入

javascript
// main.js - 导入默认导出
import Calculator from './calculator.js';
// 或者给默认导出起任意名称
import MyCalculator from './calculator.js';

const calc = new Calculator();
console.log(calc.add(5, 3)); // 8

混合导入/导出

javascript
// shapes.js - 混合导出
export const CIRCLE = 'circle';
export const SQUARE = 'square';

class Shape {
  constructor(type) {
    this.type = type;
  }
}

export default Shape;

// main.js - 混合导入
import Shape, { CIRCLE, SQUARE } from './shapes.js';

const circle = new Shape(CIRCLE);
const square = new Shape(SQUARE);

整体导入

javascript
// main.js - 整体导入所有导出
import * as MathUtils from './math.js';

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.multiply(4, 5)); // 20

导入时重命名

javascript
// main.js - 导入时重命名
import { 
  add as addNumbers, 
  multiply as multiplyNumbers 
} from './math.js';

console.log(addNumbers(2, 3)); // 5
console.log(multiplyNumbers(4, 5)); // 20

仅导入副作用

javascript
// styles.css.js - 只导入模块的副作用(如样式注入)
import './styles.css';

// polyfill.js - 只导入polyfill的副作用
import 'babel-polyfill';

动态导入

ES2020引入了动态导入,允许在需要时异步加载模块。

基本动态导入

javascript
// 动态导入返回Promise
async function loadModule() {
  const { add, multiply } = await import('./math.js');
  return add(multiply(2, 3), 1); // (2 * 3) + 1 = 7
}

// 条件导入
async function loadFeature() {
  if (window.location.search.includes('advanced=true')) {
    const { AdvancedFeature } = await import('./advanced.js');
    return new AdvancedFeature();
  } else {
    const { BasicFeature } = await import('./basic.js');
    return new BasicFeature();
  }
}

// 按需导入
document.getElementById('loadButton').addEventListener('click', async () => {
  const { renderChart } = await import('./chart.js');
  renderChart();
});

动态导入的实际应用

javascript
// 路由级别的代码分割
async function loadRoute(routeName) {
  try {
    const moduleMap = {
      'home': './routes/Home.js',
      'about': './routes/About.js',
      'contact': './routes/Contact.js'
    };
    
    const modulePath = moduleMap[routeName];
    if (!modulePath) {
      throw new Error(`未知路由: ${routeName}`);
    }
    
    const routeModule = await import(modulePath);
    return routeModule.default;
  } catch (error) {
    console.error('路由加载失败:', error);
    // 加载错误页面
    const errorModule = await import('./routes/Error.js');
    return errorModule.default;
  }
}

// 国际化动态导入
async function loadLocale(locale) {
  try {
    const translations = await import(`./locales/${locale}.js`);
    return translations.default;
  } catch (error) {
    console.warn(`本地化文件 ${locale} 加载失败,回退到默认语言`);
    const defaultTranslations = await import('./locales/en.js');
    return defaultTranslations.default;
  }
}

CommonJS (Node.js)

CommonJS是Node.js早期使用的模块系统,使用requiremodule.exports

CommonJS 基础

javascript
// math.js (CommonJS)
const PI = 3.14159;

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

function multiply(a, b) {
  return a * b;
}

// 导出方式1:逐个导出
exports.PI = PI;
exports.add = add;
exports.multiply = multiply;

// 导出方式2:整体导出
module.exports = {
  PI,
  add,
  multiply,
  subtract: (a, b) => a - b
};

// 导出方式3:默认导出
module.exports = class Calculator {
  add(a, b) {
    return a + b;
  }
};

CommonJS 导入

javascript
// main.js (CommonJS)
const { PI, add, multiply } = require('./math.js');
const Calculator = require('./calculator.js');

// 整体导入
const math = require('./math.js');

模块加载器和打包工具

浏览器中的模块

html
<!-- 在HTML中使用模块 -->
<script type="module" src="./main.js"></script>

<!-- 内联模块脚本 -->
<script type="module">
  import { add } from './math.js';
  console.log(add(2, 3));
</script>

模块路径解析

javascript
// 相对路径
import { helper } from './utils.js';
import { config } from '../config/app.js';

// 绝对路径(相对于基URL)
import { api } from '/src/services/api.js';

// 完整URL
import { library } from 'https://cdn.skypack.dev/lodash';

// Node.js内置模块(在支持的环境中)
import fs from 'fs';
import path from 'path';

高级模块模式

模块工厂模式

javascript
// moduleFactory.js
function createModule(config) {
  return {
    name: config.name,
    dependencies: config.dependencies || [],
    
    async initialize() {
      // 加载依赖
      const dependencyModules = await Promise.all(
        this.dependencies.map(dep => import(dep))
      );
      
      // 初始化逻辑
      return this;
    },
    
    getExports() {
      return {
        name: this.name,
        // 其他导出
      };
    }
  };
}

export default createModule;

// 使用模块工厂
const myModule = createModule({
  name: 'MyModule',
  dependencies: ['./dependency1.js', './dependency2.js']
});

模块装饰器模式

javascript
// decorator.js
export function withLogging(modulePath) {
  return async function() {
    console.log(`正在加载模块: ${modulePath}`);
    const module = await import(modulePath);
    console.log(`模块加载完成: ${modulePath}`);
    return module;
  };
}

// 使用装饰器
const { add } = await withLogging('./math.js')();

条件导出

javascript
// package.json 中的条件导出配置
// {
//   "name": "my-package",
//   "exports": {
//     ".": {
//       "import": "./dist/index.mjs",
//       "require": "./dist/index.cjs",
//       "default": "./index.mjs"
//     }
//   }
// }

// 在模块内部根据环境条件导出
let exportedModule;
if (typeof window !== 'undefined') {
  // 浏览器环境
  exportedModule = { ...browserAPI };
} else {
  // Node.js环境
  exportedModule = { ...nodeAPI };
}

export default exportedModule;

模块最佳实践

模块设计原则

javascript
// 1. 单一职责原则 - 每个模块只负责一个功能
// user.js - 用户相关功能
export class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

export function validateUser(user) {
  return user.name && user.email;
}

// 2. 明确的接口 - 清晰地导出需要的API
// api.js
export const API_BASE_URL = 'https://api.example.com';

export async function fetchUser(id) {
  // 实现
}

export async function updateUser(user) {
  // 实现
}

// 不要导出内部实现细节
// export function _internalHelper() { ... } // 避免这样做

模块性能优化

javascript
// 1. 懒加载 - 按需导入
async function initializeChart() {
  // 只在需要时加载图表库
  const { Chart } = await import('chart.js');
  return new Chart();
}

// 2. 代码分割 - 将不常用的代码分离
async function loadAdminFeatures() {
  if (user.role === 'admin') {
    const adminModule = await import('./admin-panel.js');
    return adminModule;
  }
}

// 3. 预加载重要的模块
// 在应用启动时预加载
const essentialModules = Promise.all([
  import('./router.js'),
  import('./auth.js'),
  import('./api.js')
]);

// 使用时等待预加载完成
async function initializeApp() {
  const [router, auth, api] = await essentialModules;
  // 继续初始化
}

模块测试

javascript
// calculator.js
export class Calculator {
  add(a, b) {
    return a + b;
  }
  
  multiply(a, b) {
    return a * b;
  }
}

// calculator.test.js
import { Calculator } from './calculator.js';

describe('Calculator', () => {
  let calc;
  
  beforeEach(() => {
    calc = new Calculator();
  });
  
  test('add should return sum of two numbers', () => {
    expect(calc.add(2, 3)).toBe(5);
  });
  
  test('multiply should return product of two numbers', () => {
    expect(calc.multiply(2, 3)).toBe(6);
  });
});

模块文档化

javascript
/**
 * 数学运算工具模块
 * 提供基本的数学运算功能
 * @module math
 */

/**
 * 加法运算
 * @param {number} a - 第一个加数
 * @param {number} b - 第二个加数
 * @returns {number} 两数之和
 */
export function add(a, b) {
  return a + b;
}

/**
 * 乘法运算
 * @param {number} a - 被乘数
 * @param {number} b - 乘数
 * @returns {number} 两数之积
 */
export function multiply(a, b) {
  return a * b;
}

模块生态系统

npm 包的模块化

javascript
// 使用第三方包
import _ from 'lodash';
import { debounce } from 'lodash-es';

// 或者按需导入
import debounce from 'lodash/debounce';

// 创建可发布的模块
// index.js
export { default as Component } from './Component.js';
export { utils } from './utils.js';
export { constants } from './constants.js';

模块兼容性处理

javascript
// 处理ESM和CommonJS的兼容性
let createLogger;
if (typeof module !== 'undefined' && module.exports) {
  // CommonJS环境
  createLogger = require('./logger.cjs').default;
} else {
  // ESM环境
  import('./logger.mjs').then(module => {
    createLogger = module.default;
  });
}

// 或者使用动态导入处理兼容性
async function loadLogger() {
  if (typeof window !== 'undefined') {
    // 浏览器环境
    return await import('./logger.browser.js');
  } else {
    // Node.js环境
    return await import('./logger.node.js');
  }
}

JavaScript模块系统提供了强大的代码组织能力,正确使用模块系统可以让代码更加模块化、可维护和可重用。