创见博客
commonJS与ES6的module
七崽爱吃小饼干2026/09/15阅读 3

ES6 Module 介绍

ES6 Module 是 ECMAScript 6 推出的官方原生模块化方案,用于解决 JavaScript 早期没有模块化、全局变量污染、依赖混乱、无统一标准等问题,是目前前端工程化、大型项目开发的主流模块化规范。

一、为什么需要 ES6 Module

在 ES6 Module 出现之前,JavaScript 没有语言层面的模块系统,代码只能靠一个个 <script> 标签拼接,随之而来一系列问题。

1. 全局变量污染

所有脚本共享同一个全局作用域,变量都挂到 window 上,稍不注意就会互相覆盖。

html
<!-- index.html:靠顺序拼接脚本 -->
<script src="./a.js"></script>
<script src="./b.js"></script>
javascript
// a.js
var count = 0;
function increment() {
  count++;
}

// b.js
var count = 100; // 覆盖了 a.js 里的 count,a.js 的逻辑被破坏

2. 依赖顺序靠人工维护

脚本之间谁依赖谁无法声明,只能人为保证 <script> 的先后顺序;顺序写错,运行时就会报错或拿到 undefined。

html
<!-- 必须先加载 utils,再加载 main,否则 main 里找不到 formatTime -->
<script src="./utils.js"></script>
<script src="./main.js"></script>

3. 没有统一标准

社区先后出现了 IIFE、AMD、CMD、CommonJS 等方案,浏览器和 Node.js 各用各的,同一份代码往往无法通用,也缺少统一的规范约束。

4. 无法做静态优化

打包工具无法从代码中分析出哪些导出被真正使用,自然也就无法安全地剔除无用代码。

ES6 Module 正是为了一次性解决这四类问题:

早期问题ES6 Module 的解法
全局变量污染每个模块拥有独立的模块作用域,变量默认不对外暴露
依赖顺序混乱用 import / export 显式声明依赖,由引擎自动处理加载顺序
没有统一标准成为 ECMAScript 官方标准,浏览器与 Node.js 通用
无法静态优化import / export 是静态结构,编译期即可分析,支持 Tree‑Shaking
javascript
// counter.js:显式导出,作用域独立
export let count = 0;
export function increment() {
  count++;
}
javascript
// main.js:显式导入,依赖关系一目了然,也不用再关心加载顺序
import { count, increment } from './counter.js';
increment();
console.log(count); // 1

// 即使 a.js、b.js 都定义了 count,也不会互相污染
html
<script type="module" src="./main.js"></script>

二、核心作用

  • 实现代码拆分与复用,每个模块拥有独立作用域,避免全局变量冲突。
  • 明确依赖关系,让代码结构更清晰、可维护。
  • 支持静态编译,可实现 Tree‑Shaking 优化打包体积。
  • 统一浏览器与 Node.js 的模块化标准,取代之前 CommonJS、AMD、CMD 等混乱局面。

三、导出方式

1. 命名导出

一个模块可导出多个变量、函数或类,导入时必须使用相同的名字。

javascript
// math.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

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

// 也可以先在模块内声明,最后统一导出,并支持重命名
const _minus = (a, b) => a - b;
export { _minus as minus };

2. 默认导出

一个模块只能有一个默认导出,导入时可以自定义名字。

javascript
// logger.js
export default function log(msg) {
  console.log(`[log] ${msg}`);
}

3. 混合导出

命名导出与默认导出可以同时存在。

javascript
// api.js
export const BASE_URL = '/api';

export function get(url) {
  return fetch(BASE_URL + url);
}

export default function post(url, data) {
  return fetch(BASE_URL + url, {
    method: 'POST',
    body: JSON.stringify(data),
  });
}
javascript
// main.js —— 默认导出写在花括号外,命名导出写在花括号内
import post, { BASE_URL, get } from './api.js';

四、导入方式

1. 导入命名导出

javascript
import { a, fn } from './module.js';

支持在导入时重命名,避免命名冲突。

javascript
import { add as sum, PI } from './math.js';
console.log(sum(1, 2)); // 3

2. 导入默认导出

javascript
import defaultFn from './module.js';

3. 全部导入

把模块的所有导出收集到一个命名空间对象中。

javascript
import * as math from './math.js';
console.log(math.PI, math.add(1, 2), new math.Circle(2));

// 注意:命名空间对象是「只读」的,不能修改
math.PI = 3; // SyntaxError(严格模式下报错)

4. 只执行模块(无导入)

只想执行模块内的副作用代码,不需要导入任何变量。

javascript
import './polyfill.js';

五、ES6 Module 与 CommonJS 的核心区别

  1. 加载时机

    • CommonJS:运行时动态加载,require 执行到哪一行才加载哪个模块。
    • ES6 Module:编译时静态加载,import 在代码执行前就确定了依赖关系。
  2. 导出值性质

    • CommonJS:导出是值的拷贝,模块内部后续的修改不会反映到导入方。
    • ES6 Module:导出是值的引用(实时绑定),原模块更新,导入处同步变化。
javascript
// counter.cjs (CommonJS)
let count = 0;
function increment() {
  count++;
}
// 导出的是此刻 count 的「快照」,值为 0
module.exports = { count, increment, getCount: () => count };
javascript
// main.cjs
const { count, increment, getCount } = require('./counter.cjs');
increment();
console.log(count);     // 0 —— 值拷贝,外部拿到的永远是旧值
console.log(getCount()); // 1 —— 通过闭包读取内部最新值
javascript
// counter.js (ESM)
export let count = 0;
export function increment() {
  count++;
}
javascript
// main.js
import { count, increment } from './counter.js';
increment();
console.log(count); // 1 —— 实时绑定,跟随原模块变化
javascript
import { count } from './counter.js';
count = 100; // TypeError —— 导入的绑定是只读的,不能直接赋值
  1. 严格模式

    • CommonJS:默认非严格模式。
    • ES6 Module:自动启用严格模式,无需手写 'use strict'。
  2. 顶层 this

    • CommonJS:this 指向当前模块的 module.exports。
    • ES6 Module:this 为 undefined。
javascript
// CommonJS
console.log(this === module.exports); // true

// ESM
console.log(this); // undefined
  1. 环境与优化
    • CommonJS:主要用于 Node.js,依赖运行时分析,不支持 Tree‑Shaking。
    • ES6 Module:浏览器与 Node 通用,静态结构可被分析,支持 Tree‑Shaking。
javascript
// utils.js
export function used() {
  return 'used';
}
export function unused() {
  return 'unused';
}
javascript
// main.js
import { used } from './utils.js';
console.log(used());
// 打包后 unused 无人引用,会被 Tree‑Shaking 移除

Tree-Shaking(摇树优化)是 ES6 Module 专属的代码优化技术,核心是在打包时,自动剔除项目中未被使用的无用代码,减少打包体积、提升运行性能。

  1. 动态加载
    • CommonJS:require 天然支持动态路径与条件加载。
    • ES6 Module:静态 import 必须在顶层且路径为字符串字面量,动态加载需使用 import()。
javascript
// CommonJS:路径可以是变量
const plugin = require(`./plugins/${name}`);
javascript
// ESM:使用 import(),返回 Promise
async function loadChart(name) {
  const module = await import(`./plugins/${name}.js`);
  module.render();
}

loadChart('bar');

六、使用特点

  • 模块内变量、函数默认局部作用域,不污染全局。
  • 浏览器中使用需在 <script> 标签添加 type="module"。
html
<script type="module" src="./main.js"></script>

type="module" 的脚本默认具有以下行为:

  • 自动延迟执行(defer),等 HTML 解析完后再执行,不会阻塞页面。

  • 自动启用严格模式。

  • 每个模块拥有独立作用域,顶层变量不会挂到 window 上。

  • 同一模块多次 import 只会执行一次,结果被缓存。

  • 受 CORS 限制,通过 file:// 直接打开会报错,需要本地服务器或正确的响应头。

  • 导入路径必须完整,支持相对路径与绝对路径。

    • 浏览器原生 ESM 必须写全后缀(./utils.js),不能省略 .js;Webpack / Vite 等构建工具会自动补全,所以两者行为有差异。
javascript
import { add } from './math.js';      // 浏览器原生:正确
import { add } from './math';         // 浏览器原生:报错,构建工具:可用
  • 是现代前端工程化(Webpack/Vite/Rollup)的基础模块化规范。
评论
0/100