说说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通过exportimport关键字构建模块体系,语法设计兼顾易用性与静态分析能力。

(一)导出语法:三种核心导出方式

  1. 命名导出:支持单个、批量及重命名导出

    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 }; // 批量+重命名导出
  2. 默认导出:每个模块唯一的默认出口,导入时可自定义名称

    1
    2
    3
    4
    5
    // calculator.js
    export default class Calculator { // 默认导出类
    constructor() {}
    sum(a,b) { return a+b; }
    }
  3. 复合导出:转发其他模块导出,适用于构建模块入口

    1
    2
    3
    // index.js
    export * from './utils.js'; // 转发所有命名导出
    export { default as Calculator } from './calculator.js'; // 转发默认导出

(二)导入语法:灵活适配使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 导入命名成员
import { PI, add, mul } from './utils.js';

// 2. 导入默认成员(自定义名称)
import Calc from './calculator.js';

// 3. 混合导入
import Calc, { PI } from './index.js';

// 4. 重命名与整体导入
import { add as plus, mul as multiply } from './utils.js';
import * as utils from './utils.js'; // 命名空间对象

// 5. 远程模块导入(浏览器原生支持)
import { createStore } from "https://unpkg.com/redux@4.0.5/es/redux.mjs";

(三)语法铁律:保障静态特性

import/export必须置于代码顶层,禁止嵌套在条件语句或函数中:

1
2
3
4
// 错误示例
if (true) {
import { add } from './utils.js'; // 语法报错
}

本质差异:重新定义模块化能力

ES6 Module与传统方案的核心区别体现在加载机制、值传递方式等底层逻辑上。

(一)静态加载 vs 动态加载

特性 ES6 Module CommonJS
加载时机 编译期静态分析 运行时动态加载
依赖分析 支持静态依赖树构建 依赖关系动态变化
代码优化 原生支持Tree-shaking 无法实现死代码剔除
动态加载能力 通过import()函数实现 require()天然支持

Tree-shaking优化需配合package.json配置:

1
2
// package.json 关键配置
{ "sideEffects": false } // 标记无副作用模块
1
2
3
// 未使用的函数会被Tree-shaking剔除
import { usedFunc, unusedFunc } from './utils.js';
usedFunc(); // 最终打包仅包含usedFunc

(二)引用绑定 vs 值拷贝

ES6 Module采用实时引用绑定,导出值更新会同步影响导入方:

1
2
3
4
5
6
7
8
9
// 模块A:moduleA.js
export let count = 0;
export const increment = () => count++;

// 模块B:moduleB.js
import { count, increment } from './moduleA.js';
console.log(count); // 0
increment();
console.log(count); // 1(实时更新)

CommonJS则是值拷贝快照,后续更新不影响已导入值:

1
2
3
4
5
6
7
8
9
10
11
12
// 模块A:moduleA.cjs
let count = 0;
module.exports = {
count,
increment: () => count++
};

// 模块B:moduleB.cjs
const { count, increment } = require('./moduleA.cjs');
console.log(count); // 0
increment();
console.log(count); // 0(值未更新)

(三)内置特性:提升开发规范性

  1. 默认严格模式:模块内自动启用'use strict',禁止未声明变量赋值
  2. 独立模块作用域:内部变量不会泄漏到全局,解决命名冲突问题
  3. 单例特性:模块仅执行一次,多次导入复用同一实例
    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双端覆盖,配合工具链可适配全场景需求。

(一)浏览器端应用

  1. 原生使用:通过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>
  2. 兼容性处理:旧浏览器需通过Babel转译+Webpack打包,将ES6 Module转为ES5代码

(二)Node.js端应用

  1. 启用方式:二选一即可

    • 配置package.json: "type": "module"
    • 文件后缀改为.mjs
  2. 与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
2
3
4
5
6
// 按钮点击时加载模块(首屏性能优化)
const btn = document.getElementById('load-btn');
btn.addEventListener('click', async () => {
const { default: Chart } = await import('./chart.js');
new Chart('#canvas'); // 动态初始化图表
});

该特性已成为前端路由懒加载的核心技术,如React的React.lazy本质就是对import()的封装。

写在最后:一个老开发的个人看法

如今,ES6 Module 已成为现代 JavaScript 开发的基础设施:WebpackRollup 等构建工具以其静态特性实现高效打包,ReactVue 等框架借助动态导入优化加载性能,Node.js 与浏览器的原生支持更让 “一次编写、两端运行” 成为现实。

对于开发者而言,理解 ES6 Module 的本质不仅是掌握语法规则,更是把握 JavaScript 生态演进的核心逻辑 —— 从 “解决当下问题” 到 “构建可持续生态”,这正是模块化标准从混乱走向统一的根本意义,也为 JavaScript 在大型应用与跨端开发领域的持续突破奠定了坚实基础。

【往期精彩】


说说JavaScript中ES6 module(模块化)的诞生背景、核心语法及应用场景
https://www.jvxiao.cn/posts/module-in-es6.html
Author
jvxiao
Posted on
December 6, 2025
Licensed under