说说JavaScript中ES6 module(模块化)的诞生背景、核心语法及应用场景
Last updated on December 6, 2025 am
这是JavaScript进阶系列文章的第10篇文章, 今天我们要讲的是 JavaScript ES6中一个非常核心的概念,它的出现可以说是统一了JavaScript中模块化规范的三国时代,那么它是什么呢–ES6 Module
诞生背景:终结混乱的模块化革命
在ES6 Module出现之前,JavaScript长期缺乏官方模块化标准,前端开发深陷”全局变量污染-命名冲突-依赖混乱”的恶性循环。2010年前后崛起的三大方案各有局限:
- CommonJS:为Node.js设计的同步加载方案,无法适配浏览器异步环境,
require('./module' + num)的动态加载特性也埋下了依赖分析的隐患 - AMD:以RequireJS为代表的异步方案,虽解决了浏览器加载问题,但
define函数嵌套层级深,语法冗余度高 - UMD:兼容多环境的妥协方案,通过大量条件判断实现跨平台,导致代码体积膨胀且维护困难
2015年ES6正式引入Module规范,标志着JavaScript首次在语言层面确立模块化标准。其核心使命是:打通前后端模块化壁垒,提供兼顾语法简洁性、加载高效性与工程可扩展性的统一方案。
截至2024年,主流浏览器对ES6 Module的支持率已达97.24%,Node.js自v12.17.0起实现原生支持,彻底改变了JavaScript的开发范式。
核心语法:简洁而严谨的模块交互规则
ES6 Module通过export与import关键字构建模块体系,语法设计兼顾易用性与静态分析能力。
(一)导出语法:三种核心导出方式
命名导出:支持单个、批量及重命名导出
1
2
3
4
5// utils.js
export const PI = 3.14; // 单个导出
const add = (a,b) => a+b;
const multiply = (a,b) => a*b;
export { add, multiply as mul }; // 批量+重命名导出默认导出:每个模块唯一的默认出口,导入时可自定义名称
1
2
3
4
5// calculator.js
export default class Calculator { // 默认导出类
constructor() {}
sum(a,b) { return a+b; }
}复合导出:转发其他模块导出,适用于构建模块入口
1
2
3// index.js
export * from './utils.js'; // 转发所有命名导出
export { default as Calculator } from './calculator.js'; // 转发默认导出
(二)导入语法:灵活适配使用场景
1 | |
(三)语法铁律:保障静态特性
import/export必须置于代码顶层,禁止嵌套在条件语句或函数中:
1 | |
本质差异:重新定义模块化能力
ES6 Module与传统方案的核心区别体现在加载机制、值传递方式等底层逻辑上。
(一)静态加载 vs 动态加载
| 特性 | ES6 Module | CommonJS |
|---|---|---|
| 加载时机 | 编译期静态分析 | 运行时动态加载 |
| 依赖分析 | 支持静态依赖树构建 | 依赖关系动态变化 |
| 代码优化 | 原生支持Tree-shaking | 无法实现死代码剔除 |
| 动态加载能力 | 通过import()函数实现 |
require()天然支持 |
Tree-shaking优化需配合package.json配置:
1 | |
1 | |
(二)引用绑定 vs 值拷贝
ES6 Module采用实时引用绑定,导出值更新会同步影响导入方:
1 | |
CommonJS则是值拷贝快照,后续更新不影响已导入值:
1 | |
(三)内置特性:提升开发规范性
- 默认严格模式:模块内自动启用
'use strict',禁止未声明变量赋值 - 独立模块作用域:内部变量不会泄漏到全局,解决命名冲突问题
- 单例特性:模块仅执行一次,多次导入复用同一实例
1
2
3
4
5
6
7
8
9// 计数器模块:counter.js
let count = 0;
export const getCount = () => ++count;
// 两次导入共享同一实例
import { getCount } from './counter.js';
import { getCount as getNewCount } from './counter.js';
console.log(getCount()); // 1
console.log(getNewCount()); // 2
场景落地:跨环境的模块化实践
ES6 Module已实现浏览器与Node.js双端覆盖,配合工具链可适配全场景需求。
(一)浏览器端应用
原生使用:通过
type="module"启用1
2
3
4
5
6
7
8<!-- 内联模块 -->
<script type="module">
import { add } from './utils.js';
console.log(add(2,3)); // 5
</script>
<!-- 外部模块 -->
<script type="module" src="./app.js"></script>兼容性处理:旧浏览器需通过Babel转译+Webpack打包,将ES6 Module转为ES5代码
(二)Node.js端应用
启用方式:二选一即可
- 配置
package.json:"type": "module" - 文件后缀改为
.mjs
- 配置
与CommonJS互操作:
1
2
3
4
5
6
7// ES6 Module中导入CommonJS模块
import fs from 'fs'; // 自动识别package.json的main字段
// 复杂场景使用createRequire
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjsModule = require('./module.cjs');
(三)高级场景:动态导入与性能优化
import()函数返回Promise,支持按需加载与条件加载:
1 | |
该特性已成为前端路由懒加载的核心技术,如React的React.lazy本质就是对import()的封装。
写在最后:一个老开发的个人看法
如今,ES6 Module 已成为现代 JavaScript 开发的基础设施:Webpack、Rollup 等构建工具以其静态特性实现高效打包,React、Vue 等框架借助动态导入优化加载性能,Node.js 与浏览器的原生支持更让 “一次编写、两端运行” 成为现实。
对于开发者而言,理解 ES6 Module 的本质不仅是掌握语法规则,更是把握 JavaScript 生态演进的核心逻辑 —— 从 “解决当下问题” 到 “构建可持续生态”,这正是模块化标准从混乱走向统一的根本意义,也为 JavaScript 在大型应用与跨端开发领域的持续突破奠定了坚实基础。
【往期精彩】