icon
ES Module

ES Module

ES Module(ESM)是 JavaScript 的官方模块系统,提供了静态的模块导入和导出机制。

基本语法

  1. 导出(Export)
// 命名导出
export const name = 'Alice';
export function greet() {
  return 'Hello';
}
export class User {
  constructor(name) {
    this.name = name;
  }
}

// 或者统一导出
const name = 'Alice';
function greet() {
  return 'Hello';
}
export { name, greet };

// 重命名导出
export { name as userName, greet as sayHello };
  1. 导入(Import)
// 命名导入
import { name, greet } from './module.js';
import { name as userName, greet as sayHello } from './module.js';

// 导入所有
import * as module from './module.js';
module.name;
module.greet();

// 默认导入
import defaultExport from './module.js';

// 混合导入
import defaultExport, { namedExport } from './module.js';
import defaultExport, * as namedExports from './module.js';
  1. 默认导出
// 默认导出
export default function greet() {
  return 'Hello';
}

// 或者
function greet() {
  return 'Hello';
}
export default greet;

// 默认导出类
export default class User {
  constructor(name) {
    this.name = name;
  }
}

模块特性

  1. 静态分析
    • 导入导出在编译时确定
    • 支持 tree-shaking(移除未使用的代码)
// 编译时就能知道导入了什么
import { func1 } from './utils.js';
// func2 不会被包含在打包结果中(如果未使用)
  1. 严格模式
    • ES Module 自动启用严格模式
// 不需要 'use strict'
// 自动启用严格模式
  1. 顶层 this
    • 模块顶层 this 是 undefined
console.log(this); // undefined
  1. 只执行一次
    • 模块代码只执行一次,多次导入会共享同一个实例
// module.js
let count = 0;
export function increment() {
  count++;
  return count;
}

// main.js
import { increment } from './module.js';
import { increment as inc } from './module.js';

increment(); // 1
inc();       // 2 (共享同一个 count)

动态导入

使用 import() 函数实现动态导入:

// 动态导入返回 Promise
import('./module.js')
  .then(module => {
    module.default();
    module.namedExport();
  });

// 使用 async/await
async function loadModule() {
  const module = await import('./module.js');
  module.default();
}

// 条件导入
if (condition) {
  const module = await import('./module.js');
}

重新导出

// 重新导出其他模块的内容
export { name, greet } from './module1.js';
export { default } from './module2.js';
export * from './module3.js';

// 重命名重新导出
export { name as userName } from './module1.js';

实际应用

  1. 模块组织
// utils/math.js
export function add(a, b) {
  return a + b;
}

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

// utils/string.js
export function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

// utils/index.js (统一导出)
export * from './math.js';
export * from './string.js';

// 使用
import { add, capitalize } from './utils/index.js';
  1. 默认导出对象
// config.js
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

export default config;

// 使用
import config from './config.js';
console.log(config.apiUrl);
  1. 命名空间导出
// api/user.js
export function getUser(id) {
  return fetch(`/api/users/${id}`);
}

export function createUser(data) {
  return fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(data)
  });
}

// 使用命名空间
import * as userAPI from './api/user.js';
userAPI.getUser(1);
userAPI.createUser({ name: 'Alice' });
  1. 类型导出(TypeScript)
// types.ts
export interface User {
  name: string;
  age: number;
}

export type Status = 'active' | 'inactive';

// 使用
import type { User, Status } from './types.js';

与 CommonJS 的区别

| 特性 | ES Module | CommonJS | |------|-----------|----------| | 加载时机 | 编译时 | 运行时 | | 导入方式 | import | require() | | 导出方式 | export | module.exports | | 动态导入 | import() | require() | | 顶层 this | undefined | module | | 严格模式 | 自动启用 | 需手动启用 |

// CommonJS
const module = require('./module.js');
module.exports = { name: 'Alice' };

// ES Module
import module from './module.js';
export default { name: 'Alice' };

循环依赖

ES Module 可以处理循环依赖:

// a.js
import { b } from './b.js';
export const a = 'a';

// b.js
import { a } from './a.js';
export const b = 'b';

// 可以正常工作,但需要注意初始化顺序

模块路径

  1. 相对路径
import { func } from './utils.js';
import { func } from '../utils.js';
  1. 绝对路径
import { func } from '/src/utils.js';
  1. URL
import { func } from 'https://example.com/module.js';
  1. 包名(需要配置)
import React from 'react';
import { map } from 'lodash';

注意事项

  1. 文件扩展名
    • 某些环境需要显式指定 .js 扩展名
import { func } from './utils.js'; // 推荐
import { func } from './utils';    // 可能不工作
  1. 默认导出 vs 命名导出
// 默认导出
export default function() {}
import func from './module.js';

// 命名导出
export function func() {}
import { func } from './module.js';
  1. 导入提升
    • import 语句会被提升到文件顶部
// 这些都会在顶部执行
func(); // 可以工作
import { func } from './module.js';
  1. 只读绑定
    • 导入的变量是只读的
import { name } from './module.js';
name = 'Bob'; // TypeError: Assignment to constant variable

浏览器中使用

<!-- 使用 type="module" -->
<script type="module">
  import { greet } from './module.js';
  greet();
</script>

<!-- 或者外部文件 -->
<script type="module" src="./main.js"></script>

Node.js 中使用

// package.json
{
  "type": "module"
}
// 使用 .mjs 扩展名
// 或者在 package.json 中设置 "type": "module"
import { func } from './module.js';