ES6 模块化 export _ imp完整实例详解
作者:Sterting
在前几节我们掌握了 JS 的基本语法、函数和数组操作后,代码量自然会膨胀。后端开发者对此并不陌生:Java 中有 package 和 import,Python 中有 import 和 from ... import,C# 有 namespace 和 using。模块化正是将这些思想搬到 JS 中——让每一个文件各司其职,通过明确的接口输入输出。
本节将解决三个问题:
- 如何把变量、函数、类“分享”出去?(
export的三种写法) - 如何从其他文件“引入”需要的功能?(
import的五种写法) - 如何将一个单文件的学生管理系统拆分成干净、可维护的模块群?
1. 为什么需要模块化
当一个文件超过几百行,或者不同的逻辑(数据、筛选、渲染)都塞在一起时,代码就会变成难以维护的“大泥球”。模块化将代码拆成独立的部分,每个部分只负责一个明确的功能:
- 数据模块:单纯提供原始数据,不加工。
- 工具模块:提供可复用函数,如筛选、排序、计算。
- 渲染模块:负责把数据变成 HTML。
- 入口模块:组装所有模块,绑定交互逻辑。
这种思想和后端几乎一致:你早就习惯了“一个类一个文件”或“一个包一组功能”,JS 的模块语法只是换了一种说法。
类比:厨房里,切菜师傅、炒菜师傅、摆盘师傅各司其职,通过“出菜单”和“接菜单”协作。
export就是告诉别人你能提供什么,import就是你需要别人提供什么。
2.export:把功能“贴标签”供人使用
JS 模块中,只有被 export 标记的变量、函数或类才能被其他文件访问。未导出的内容则是私有的,外部无法触及。
2.1 命名导出(Named Export)
可以导出任意数量的命名成员,导入时必须使用完全相同的名字(或使用别名)。
// file: math-utils.js
// 导出单个常量
export const PI = 3.1415926;
// 导出函数
export function add(a, b) {
return a + b;
}
// 也可以在文件末尾集中导出(推荐在较长的文件中使用)
const multiply = (a, b) => a * b;
const VERSION = '1.0.0';
export { multiply, VERSION };集中导出的好处是:文件开头可以专注于实现,末尾的 export {} 像一个“目录”,一眼就能看出该模块对外提供了哪些接口。
2.2 默认导出(Default Export)
一个文件只能有一个默认导出。导入时可以自由命名,不需要花括号。
// file: student-service.js
export default class StudentService {
constructor() { /* ... */ }
getStudents() { /* ... */ }
}
// 或者导出某个函数
export default function formatName(first, last) {
return `${last} ${first}`;
}默认导出适合“一个文件一个核心内容”的模式,比如单个类、单个 Vue 组件、或者一个 React 组件。
2.3 聚合导出(Re-export)
如果多个模块散落在不同文件中,可以创建一个“汇总”文件,将其他模块的导出聚合起来,让使用者只需从一个地方引入。
// file: utils/index.js
export { add, PI } from './math-utils.js';
export { default as StudentService } from './student-service.js';
export { formatDate } from './date-utils.js';
这类似于 Java 的 package-info.java 或 Python 的 __init__.py,用于建立一个模块包的统一入口。
3.import:按需取货
3.1 命名导入
语法:import { 名称 } from '模块路径'
花括号内的名字必须与导出方完全一致。
import { add, PI } from './math-utils.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159263.2 别名导入
若导入的名字过长或与其他模块冲突,可以用 as 重命名。
import { multiply as times } from './math-utils.js';
console.log(times(4, 5)); // 20
3.3 默认导入
无花括号,变量名可自定,对应模块的默认导出。
import FormatName from './string-utils.js';
// 假设 string-utils.js 的 default export 是一个函数
console.log(FormatName('三', '张')); // 张三
3.4 命名空间导入
把整个模块的所有导出打包成一个对象。
import * as MathUtils from './math-utils.js'; console.log(MathUtils.add(10, 5)); console.log(MathUtils.PI);
这很像 Python 的 import math 之后通过 math.xxx 访问,或者 Java 的 import java.util.*;(但 JS 并不展开到当前作用域,而是整理成一个命名空间)。
3.5 混合导入
可以同时引入默认导出和若干命名导出。
import StudentService, { VERSION as ver, validate } from './student.js';
// StudentService 是默认导出
// ver 和 validate 是命名导出
4. 模块路径与运行环境
4.1 路径规则
- 相对路径必须以
./或../开头,否则浏览器会将其视为裸模块(如'lodash'),去寻找 node_modules。 - 必须写文件扩展名
.js(与 Node.js 或打包工具不同,原始 ES 模块要求完整后缀)。 - 导入的是只读引用,不能为导入变量重新赋值。
4.2 为什么需要 HTTP 服务器?
在浏览器中使用 ES 模块(<script type="module">)时,import 会发起 HTTP 请求加载对应的 JS 文件。出于安全策略,浏览器禁止从 file:// 协议加载模块。因此必须使用本地静态服务器,例如:
# 在代码目录下执行(任选一种) python -m http.server 3000 # Python 内置,无需安装 npx serve . # 需 Node.js 环境 # 或者使用 VS Code 的 Live Server 插件
启动后,通过 http://localhost:3000 访问页面。
5. 实战:拆分学生管理系统
假设我们有一个学生管理页面,原本所有逻辑都写在一个 HTML 文件内。现在我们将其拆分成几个模块。
文件结构:
student-app/ ├── index.html ├── app.js (入口,负责调度) ├── student-data.js (数据模块) ├── student-filter.js (筛选逻辑) ├── student-stats.js (统计计算) └── student-render.js (渲染模块)
5.1 数据模块
仅导出原始数据和一个辅助对象,不做任何处理。
// student-data.js
export const students = [
{ id: 1, name: '张三', score: 92, class: '1班' },
{ id: 2, name: '李四', score: 78, class: '1班' },
// ...
];
export const CLASS_MAP = {
'1班': { teacher: '张老师', room: '301' },
// ...
};5.2 筛选模块
提供独立的筛选函数,每个函数接收数据数组作为参数,返回新数组(不修改原数组)。
// student-filter.js
export function filterByScore(list, minScore) {
return list.filter(s => s.score >= minScore);
}
export function filterByName(list, keyword) {
if (!keyword) return list;
return list.filter(s => s.name.includes(keyword));
}
export function filterByClass(list, className) {
if (!className) return list;
return list.filter(s => s.class === className);
}这里大量使用了第 4 节学到的 filter 方法,并利用纯函数的方式保证可复用性。
5.3 统计模块
使用 map、reduce、sort 计算各种统计值。
// student-stats.js
export function calcAverage(list) {
if (list.length === 0) return 0;
const total = list.reduce((sum, s) => sum + s.score, 0);
return (total / list.length).toFixed(1);
}
export function getTopN(list, n = 3) {
return [...list] // 拷贝,避免修改原数组
.sort((a, b) => b.score - a.score)
.slice(0, n);
}
export function getPassRate(list) {
if (list.length === 0) return '0%';
const passed = list.filter(s => s.score >= 60).length;
return ((passed / list.length) * 100).toFixed(1) + '%';
}5.4 渲染模块
负责将数据转换为 HTML 字符串,使用模板字符串简化拼接。
// student-render.js
export function renderTable(list) {
if (!list.length) return '<p>暂无数据</p>';
const rows = list.map((s, i) => `
<tr>
<td>${i + 1}</td>
<td>${s.name}</td>
<td>${s.class}</td>
<td>${s.score}</td>
</tr>
`).join('');
return `<table>${rows}</table>`;
}5.5 入口模块
app.js 不包含具体实现,只负责导入和调度:
import { students } from './student-data.js';
import { filterByScore, filterByName } from './student-filter.js';
import { calcAverage, getPassRate } from './student-stats.js';
import { renderTable } from './student-render.js';
let currentData = students;
function refresh() {
document.getElementById('app').innerHTML = renderTable(currentData);
document.getElementById('avg').textContent = calcAverage(currentData);
}
document.getElementById('search').addEventListener('input', (e) => {
currentData = filterByName(students, e.target.value);
refresh();
});
document.getElementById('filter-excellent').addEventListener('click', () => {
currentData = filterByScore(students, 90);
refresh();
});
// 初始渲染
refresh();主页面 index.html 需要通过 type="module" 引入入口脚本:
<script type="module" src="./app.js"></script>
这样,各个模块各司其职,任何一个文件的修改都不会意外波及全局。
6. 小结
export将代码单元暴露给外部:命名导出可多个,默认导出只有一个,聚合导出用于整合。import从其他模块引入功能:根据需求选择命名导入、别名导入、默认导入、命名空间导入或混合导入。- ES 模块要求浏览器使用 HTTP 协议,务必通过本地服务器运行(
python -m http.server等)。 - 模块化的核心是分而治之,每个文件只负责一件事,通过明确的接口通信——这与后端编程中的包、模块、命名空间理念完全一致。
掌握了模块化,你就具备了构建中型前端应用的基础,也为后续学习组件化、状态管理铺平了道路。
到此这篇关于ES6 模块化 export _ imp完整实例详解的文章就介绍到这了,更多相关ES6 模块化 export _ imp内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
