模块化是现代 JavaScript 开发的基石。它帮助我们组织代码、管理依赖、避免命名冲突,并提升可维护性。从早期的全局函数到 ES6 原生模块,JavaScript 模块化方案经历了漫长的演进。本文将深入浅出地讲解各种模块化规范、核心语法、打包工具以及最佳实践,带你彻底掌握 JavaScript 模块化开发。
什么是模块化?
模块化是指将复杂的程序拆分成独立的、可复用的模块,每个模块只关注一个特定的功能。模块内部的作用域独立,通过特定的接口向外暴露。
// 没有模块化的时代
// 全局污染,命名冲突
function foo() { ... }
var bar = 123;
// 简单的对象命名空间
var myApp = {
foo: function() {},
bar: 123
};
// IIFE 实现私有作用域
var module = (function() {
var privateVar = 'secret';
function publicFn() { ... }
return { foo: publicFn };
})();
// 全局污染,命名冲突
function foo() { ... }
var bar = 123;
// 简单的对象命名空间
var myApp = {
foo: function() {},
bar: 123
};
// IIFE 实现私有作用域
var module = (function() {
var privateVar = 'secret';
function publicFn() { ... }
return { foo: publicFn };
})();
模块 = 独立的功能单元,组合成完整应用
JavaScript 模块化演进
// CommonJS (Node.js环境)
// 导出
module.exports = { foo };
// 导入
const bar = require('./bar');
// AMD (RequireJS 浏览器端)
define(['dep'], function(dep) {
return { ... };
});
// ES6 Module (现代标准)
export const foo = 42;
import { foo } from './module';
// 导出
module.exports = { foo };
// 导入
const bar = require('./bar');
// AMD (RequireJS 浏览器端)
define(['dep'], function(dep) {
return { ... };
});
// ES6 Module (现代标准)
export const foo = 42;
import { foo } from './module';
- 2009 CommonJS (Node)
- 2011 AMD (RequireJS)
- 2012 UMD (通用)
- 2015 ES6 模块 (官方标准)
如今 ES Module 已成为浏览器和服务端共同支持的标准。
ES6 模块核心语法
// 命名导出 (named export)
export const name = 'module';
export function util() { }
// 默认导出 (default export)
export default class { ... }
// 导入
import { name, util } from './module';
import AnyName from './module'; // 默认导入
import * as all from './module'; // 整体导入
// 动态导入 (返回Promise)
import('./module').then(module => { ... });
export const name = 'module';
export function util() { }
// 默认导出 (default export)
export default class { ... }
// 导入
import { name, util } from './module';
import AnyName from './module'; // 默认导入
import * as all from './module'; // 整体导入
// 动态导入 (返回Promise)
import('./module').then(module => { ... });
export 对外暴露接口
import 引入依赖
动态 import() 按需加载
关键特性:ES6 模块是静态的,可以在编译时确定依赖关系,从而实现 tree shaking 和更高效的打包。
模块打包工具
// webpack.config.js 示例
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' }
]
}
};
// Rollup 专注于 ES模块 打包
// Vite 基于原生ESM的开发服务器
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' }
]
}
};
// Rollup 专注于 ES模块 打包
// Vite 基于原生ESM的开发服务器
index.js
webpack
bundle.js
打包工具分析依赖图,合并、压缩模块。
主流工具对比:Webpack 功能全面,Rollup 更适合库打包,Vite 开发体验极佳。
实战:ES6 模块案例
// --- math.js ---
export const add = (a, b) => a + b;
export const PI = 3.1416;
// --- logger.js ---
export default function log(msg) {
console.log(`[LOG]: ${msg}`);
}
// --- app.js ---
import log from './logger.js';
import { add, PI } from './math.js';
log(`2 + 3 = ${add(2, 3)}`);
log(`圆周率约等于 ${PI}`);
export const add = (a, b) => a + b;
export const PI = 3.1416;
// --- logger.js ---
export default function log(msg) {
console.log(`[LOG]: ${msg}`);
}
// --- app.js ---
import log from './logger.js';
import { add, PI } from './math.js';
log(`2 + 3 = ${add(2, 3)}`);
log(`圆周率约等于 ${PI}`);
app.js
math.js
logger.js
浏览器中通过 <script type="module"> 直接使用,或打包后部署。
模块化最佳实践
// 1. 单一职责:每个模块只做一件事
// 2. 避免循环依赖
// 3. 使用命名导出而不是默认导出(便于重构和tree shaking)
// 4. 动态 import 实现路由懒加载
// 示例:按需加载
button.addEventListener('click', async () => {
const { showDialog } = await import('./dialog.js');
showDialog();
});
// 2. 避免循环依赖
// 3. 使用命名导出而不是默认导出(便于重构和tree shaking)
// 4. 动态 import 实现路由懒加载
// 示例:按需加载
button.addEventListener('click', async () => {
const { showDialog } = await import('./dialog.js');
showDialog();
});
Tree Shaking 消除死代码,依赖静态的 ES 模块结构。
性能优化与 Tree Shaking
// 未被使用的导出会被移除 (基于副作用)
export const used = 42;
export const unused = 100; // 如果未导入,最终会被shake掉
// package.json 中标记 "sideEffects": false 帮助打包工具优化
export const used = 42;
export const unused = 100; // 如果未导入,最终会被shake掉
// package.json 中标记 "sideEffects": false 帮助打包工具优化
原始体积 150KB
Tree Shaken 后 90KB
使用支持 ES 模块的库(如 lodash-es),配合打包工具的 sideEffects 配置,显著减小打包体积。
总结
JavaScript 模块化经历了从无到有,从社区规范到语言内置的过程。掌握 ES6 模块是今天前端开发的必备技能,结合 Webpack/Vite 等工具,我们可以构建出可维护、高性能的应用。
建议在实际项目中全部采用 ES Module 写法,并使用打包工具进行优化。对于老项目,可以通过渐进式重构逐步模块化。