首页 / 中文教程 / 教程

[笔记] JavaScript 模块(ES6 Modules)

作者最近开始了转全栈开发的旅程,正在写一个系列,记录每天学到的东西。这篇关于 ES6 模块 的文章就是系列的第一篇。

Ray Chan·2026-08-12·约 3 分钟

作者最近开始了转全栈开发的旅程,正在写一个系列,记录每天学到的东西。这篇关于 ES6 模块 的文章就是系列的第一篇。

注意:如果还不熟悉变量、函数、类和对象这些 JS 概念,这篇内容可能不太好懂。

引言(Intro)

  • importexport 两个关键字。
  • 模块化 = 每个文件都是它自己的模块;文件里定义的常量、变量、函数和类默认只属于该模块(私有),除非显式导出。
  • 一个模块里导出的值,可以被显式导入它们的其他模块使用。
  • 模块与普通脚本的区别:在普通脚本里,顶层声明的变量、函数和类会进入所有脚本共享的同一个全局上下文;而模块中,每个文件都有自己的私有上下文。
  • ES6 模块内的代码自动处于严格模式(strict mode),不需要在文件顶部加 use strict 指令。
  • 严格模式修复了语言的一些缺陷,提供更强的错误检查,提高安全性。
  • 严格模式下所有变量都必须声明。

ES6 导出(Exports)

可以从模块导出的符号有:常量、变量、函数、类。

export const PI = Math.PI;

export function degreesToRadians(d) { return d * PI / 180; }

export class Circle {
	constructor(r) { this.r = r; }
	area() { return PI * this.r * this.r; }
}

除了在每个实体前单独加 export 关键字,也可以在结尾统一列出:

export { Cirlce, degreesToRadians, PI };

这里的花括号并不表示对象的解构(destructuring)。

模块通常只导出一个值(典型是函数或类):

export default class BitSet {
	// implementation omitted
}

导出分两种类型:

  • 常规导出(regular exports)
  • 默认导出(default exports)

export default 的默认导出可以导出任意表达式,包括匿名函数表达式和匿名类表达式。默认导出时如果出现花括号,表示的是对象字面量。

ES6 导入(Imports)

import BitSet from './bitset.js';

指定模块(与当前模块同目录的 bitset.js 文件)的默认导出,会成为当前模块中该标识符的值。

当模块把单个值作为默认导出时,导入它不需要花括号;如果是命名导出,就要用花括号。

在现代 JavaScript 环境里,模块说明符(module specifier)经常省略 .js 扩展名。

导入进来的值是一个 const 变量。

从外部包导入:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

'react-router-dom' 是外部包,所以不需要相对路径。这告诉模块系统去 node_modules 里查找这个包。

从导出多个值的模块导入:

import { mean, stddev } from './stats.js';

对于没有名字的默认导出,导入时需要自己提供一个本地名字。

对于命名/常规导出,导入时沿用它们原来的名字。

从定义了很多导出的模块导入:

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

这样的 import 语句会创建一个对象并赋给名为 stats 的常量。被导入模块的每个非默认导出都会成为这个 stats 对象的属性。

调用方式:stats.mean()stats.stddev()

引入一个没有导出的模块(即不导出任何值的模块):

import './analytics.js';

这个被引入的模块只会运行一次。

导入与导出的重命名(Imports and Exports with Renaming)

使用 as 关键字。

import { render as renderImage } from './imageutils.js';
import { render as renderUI } from './ui.js';

再导出(Re-Exports)

一个模块可以从其他模块导入符号,再把它们导出。

stats/stats.js 中:

import { mean } from './stats/mean.js';
import { stddev } from './stats/stddev.js';
export { mean, stddev };

上面这段可以合并成一条「再导出」语句:

export { mean } from './stats/mean.js';
export { stddev } from './stats/stddev.js';

假设我们想再导出 mean() 函数,同时用 average 作为它的别名:

export { mean, mean as average } from './stats/mean.js';
export { stddev } from './stats/stddev.js';

如果 mean.jsstddev.js 里的函数不是命名导出,而是默认导出:

export { default as mean } from './stats/mean.js';
export { default as stddev } from './stats/stddev.js';

把另一个模块的命名符号再导出为当前模块的默认导出:

export { mean as default } from './stats.js';

Web 上的 JS 模块

如果想在浏览器里原生使用 import 指令,必须用 <script type="module"> 标签告诉浏览器你的代码是一个模块。

<script type="module">import './main.js';</script>

这种内联的 JS 代码叫做内联模块(inline module)。

type="module" 属性的脚本,加载和执行方式与带 defer 属性的脚本一样:HTML 解析器一遇到 <script> 标签就开始加载代码。对模块来说,这个加载步骤可能是递归的,会加载多个 JS 文件。但代码执行要等 HTML 解析完成后才开始;一旦 HTML 解析完成,脚本(无论模块还是普通脚本)按它们在 HTML 文档中出现的顺序依次执行。

如果希望内联模块在代码加载完就立即执行,加上 async 属性——这样就不用等 HTML 解析完成。

普通 <script> 标签可以从互联网上的任何服务器加载 JS 文件;但 <script type="module"> 的代码只能从包含它的 HTML 文档同源(same origin)加载,或者在有正确的 CORS 头允许跨源加载时才能加载。

使用 import() 动态导入

静态导入:

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

模块说明符 './stats.js' 不能用表达式代替——静态导入的说明符必须是字符串字面量。

动态导入:

import('./stats.js').then(stats => {
	let average = stats.mean(data);
});

import('./stats.js') 返回一个 Promise 对象,代表加载并运行指定模块的异步过程。动态导入完成后,Promise 兑现(fulfilled),产生一个对象——就像前面静态导入中的 stats 变量。

或者,在 async 函数里:

async analyzeData(data) {
	let stats = await import('./stats.js');

	return {
		average: stats.mean(data);
		stddev: stats.stddev(data);
	};
}

使用 import() 时,模块说明符可以是一个能求值为字符串的表达式。

import() 不是函数调用,而是一个运算符(operator)。

import.meta.url

import.meta 对象包含当前正在执行的模块的元数据。

import.meta.url 是模块被加载时的(绝对)URL。

例如:

function localStringsURL(locale) {
	return new URL(`l10n/${locale}.json`, import.meta.url);
}

new 关键字实例化 URL 接口时会调用它的构造函数,构造函数接收 2 个参数:第一个是绝对 URL 或相对于基础 URL 的引用(这里就是这种情况),第二个是基础 URL。

小结

这篇笔记把 ES6 模块的核心概念过了一遍:export / import 语法、默认导出与命名导出的区别、重命名(as)、再导出、浏览器中的模块加载(type="module"defer / async)、动态导入 import() 以及 import.meta.url——覆盖了日常开发中最常用的模块化写法。

💡 译者注:原文是作者的学习笔记,代码示例按原样保留(如 Cirlce 的拼写、async analyzeData 的写法、对象字面量里的分号),部分示例是语法演示而非可直接运行的完整代码。

需要帮忙?

用 Bricks 建站?我接客户项目。

从快速营销站到完整的 Bricks 建站,再到从 Elementor 迁移——我都做过。本站每篇教程都来自真实项目经验。告诉我你的需求,一个工作日内回复。

  • Bricks 建站与改版
  • Elementor / Divi → Bricks 迁移
  • Bricks → Astro / headless 性能升级
  • 速度优化,PageSpeed 95+ 目标