JavaScript 原理

ESM 模块化语法解析

了解 ES6 的 ESM 模块化机制,掌握如何使用 import/export 来组织前端代码。

🎯 引言

学完这篇文章,你将理解 ES6 的 ESM(ECMAScript Modules)模块化标准,掌握如何使用 importexport 来拆分和组织代码。这对于提升代码的可维护性和复用性非常重要。还能更好地适配现代前端构建工具和浏览器环境,避免命名冲突,让你写出更有条理的 JavaScript 应用。


📦 什么是 ESM 模块化?

模块化其实就是把复杂的代码拆解成一个个小块,每块专注做一件事。在之前的 JavaScript 世界里,模块化方案比较多,比如 CommonJS(Node.js 常用)和 AMD,但它们都不是标准。ES6 以后,JavaScript 官方推出了自己的模块化机制,叫 ESM(ECMAScript Module),简称模块。

ESM 是 JavaScript 的原生模块系统,浏览器和 Node.js 都支持。它允许你使用 export 导出变量、函数或类,再用 import 把它们引入到别的文件中,完美实现代码拆分和复用。


🚀 ESM 的基本用法

导出(export)

你可以导出变量、函数、对象,甚至类:

// math.js
export const PI = 3.14159;

export function add(x, y) {
    return x + y;
}

export class Calculator {
    subtract(a, b) {
        return a - b;
    }
}

你还可以一次性导出:

const name = 'ES6 ESM';
function greet() {
    console.log('Hello ESM!');
}

export { name, greet };

导入(import)

导入时,可以选择导入所有导出的成员,或者部分导入:

import { PI, add } from './math.js';

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

你也可以给导入的内容起别名:

import { add as sum } from './math.js';

console.log(sum(5, 6)); // 11

导入整个模块到一个对象:

import * as math from './math.js';

console.log(math.PI);
console.log(math.add(1, 2));

🧩 默认导出和命名导出

ESM 支持两种导出方式:

  • 命名导出:上面例子用的就是,导出时写名字,导入时也用对应名字。
  • 默认导出:一个模块可以有且只有一个默认导出,适合导出主功能。

举个默认导出例子:

// logger.js
export default function log(message) {
    console.log('Log:', message);
}

导入默认导出时,不用花括号:

import log from './logger.js';

log('Hello ESM!');

默认导出也可以是变量、对象、数组或类:

export default {
    version: '1.0',
    info() {
        console.log('ESM 模块信息');
    },
};

🔄 ESM 特点和注意事项

  • ESM 文件默认采用严格模式(strict mode),不用自己写 'use strict'
  • 模块内的变量默认是模块作用域,外部访问不到。
  • 导入是静态分析的,编译时就确定依赖关系,不支持动态加载(ES2020 有动态 import(),稍后会说)。
  • 不能在模块顶层使用 var,推荐用 letconst
  • 模块加载是异步的,但浏览器会保证模块之间的依赖顺序。
  • ESM 只支持相对路径或绝对路径导入,不能省略扩展名(在浏览器环境中)。

🧪 实际应用场景举例

假设你有一个网页项目,写了很多函数处理日期、字符串和数组。把它们放一个文件里,代码会越来越乱。利用 ESM,把功能拆成不同模块:

// dateUtils.js
export function formatDate(date) {
    return date.toISOString().slice(0, 10);
}

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

// arrayUtils.js
export function unique(arr) {
    return [...new Set(arr)];
}

主文件里这样引用:

import { formatDate } from './dateUtils.js';
import { capitalize } from './stringUtils.js';
import { unique } from './arrayUtils.js';

console.log(formatDate(new Date()));
console.log(capitalize('hello'));
console.log(unique([1, 2, 2, 3]));

这样结构清晰,便于维护,也方便多人协作。


提示

使用 ESM 时,浏览器环境下要通过 <script type="module"> 标签来引入模块:

<script type="module" src="./main.js"></script>

这样浏览器才会识别这是模块,会支持 importexport


注意

如果想在 Node.js 中使用 ESM 模块,你需要在 package.json 中设置 "type": "module",或者用 .mjs 后缀,否则默认是 CommonJS。不然会出现导入错误。


🔎 动态导入 import()

ES2020 引入动态导入语法,可以动态加载模块,返回一个 Promise:

async function loadModule() {
    const math = await import('./math.js');
    console.log(math.add(1, 2)); // 3
}
loadModule();

这是解决按需加载、代码分割的利器。


🧾 小节总结

  • ES6 ESM 是 JavaScript 的官方模块化方案,使用 importexport 来拆分代码。
  • 支持命名导出和默认导出,导入时可以选择性引入模块成员。
  • 使用模块化可以让代码结构更清晰,方便维护和复用。

❓ 知识问答(Q&A)

Q:ESM 模块与 CommonJS 有什么区别?

A:ESM 是 JavaScript 标准模块系统,支持静态分析,导入导出语法是静态的;CommonJS 是 Node.js 早期模块规范,导入是动态的,用 require,不支持浏览器原生。

Q:模块文件扩展名可以省略吗?

A:浏览器环境中,导入模块一般不能省略 .js 后缀,Node.js 根据配置可省略。

Q:什么是默认导出?

A:模块只能有一个默认导出,导入时不用花括号,可以给默认导出起任意名字。


🧪 小练习

  1. 创建一个名为 utils.js 的模块,导出两个函数:double(传入数字,返回两倍数字)和默认导出函数 square(传入数字返回平方)。然后在另一个文件导入并使用它们。
// utils.js
// 在这里写代码
// main.js
// 在这里写代码,导入并使用 double 和 square 函数
  1. 使用动态导入语法,按需加载上面写的模块,并调用其中的函数。
// main.js
// 在这里写代码,动态导入 utils.js 并调用函数

🎉 恭喜你已经掌握 ES6、ESM 模块化、静态导入和动态导入技能啦!前端代码组织变得更简单,更强大!