JavaScript 模块化
JavaScript 模块化
January 19, 2021
JavaScript 模块化是现代前端开发中不可或缺的一部分。本文将深入介绍 JavaScript 模块化的发展历程、主要规范以及实践应用。
为什么需要模块化?
在早期的 Web 开发中,JavaScript 代码往往写在一个文件里或分散在多个文件中,这种方式存在许多问题:
- 命名冲突:全局作用域下的变量容易造成冲突
- 依赖管理:脚本之间的依赖关系难以管理
- 代码组织:大型应用的代码难以维护和扩展
模块化编程就是为了解决这些问题而生的。
模块化的发展历程
1. 全局函数时代
最初的模块化方案是简单的函数封装:
function module1() {
// 模块1的代码
}
function module2() {
// 模块2的代码
}
这种方式虽然实现了基本的代码组织,但仍然污染全局作用域。
2. 命名空间模式(namespace pattern)
为了减少全局变量,开发者开始使用对象作为命名空间:
var MyApp = {
module1: {
data: [],
init: function() { /*...*/ }
},
module2: {
data: [],
init: function() { /*...*/ }
}
};
3. IIFE(立即执行函数表达式)
IIFE 通过创建私有作用域来避免全局污染:
var Module = (function() {
// 私有变量
var privateVar = 'private';
// 私有方法
function privateMethod() {
return privateVar;
}
// 返回公共API
return {
publicMethod: function() {
return privateMethod();
}
};
})();
4. CommonJS
Node.js 采用的模块规范,使用 require
和 module.exports
:
// math.js
module.exports = {
add: function(a, b) {
return a + b;
}
};
// main.js
const math = require('./math');
console.log(math.add(2, 3)); // 5
5. AMD (Asynchronous Module Definition)
专为浏览器设计的异步模块加载规范:
define(['jquery'], function($) {
return {
init: function() {
$('body').addClass('loaded');
}
};
});
6. ES Modules
现代 JavaScript 的官方模块化规范,使用 import
和 export
:
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// main.js
import { add, subtract } from './math.js';
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
在我的模块管理一文中,有详细介绍 ESM 和 CJS 两种模块化的差异,可移步阅读:模块管理。
ES Modules 详解
基本语法
- 导出(export):
// 命名导出
export const name = 'ES Modules';
export function sayHello() { /*...*/ }
// 默认导出
export default class MyClass { /*...*/ }
- 导入(import):
// 导入命名导出
import { name, sayHello } from './module.js';
// 导入默认导出
import MyClass from './module.js';
// 导入全部并重命名
import * as module from './module.js';
特性和优势
- 静态分析:编译时就能确定依赖关系
- 树摇(Tree Shaking):支持删除未使用的代码
- 循环依赖处理:比 CommonJS 更好的循环依赖解决方案
- 实时绑定:导出值的更新会实时反映在导入处
最佳实践
- 使用命名导出:
- 便于静态分析
- 更好的可读性
- 支持 IDE 自动补全
// ❌ 避免
export default {
method1,
method2
};
// ✅ 推荐
export const method1 = () => { /*...*/ };
export const method2 = () => { /*...*/ };
- 明确的导入:
// ❌ 避免
import * as utils from './utils';
// ✅ 推荐
import { specific, functions } from './utils';
- 模块单一职责:每个模块只做一件事
// ❌ 避免
// userUtils.js
export const validateUser = () => { /*...*/ };
export const calculateTax = () => { /*...*/ };
// ✅ 推荐
// userValidation.js
export const validateUser = () => { /*...*/ };
// taxCalculation.js
export const calculateTax = () => { /*...*/ };
现代工具集成
1. Webpack
// webpack.config.mjs
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
2. Rollup
// rollup.config.js
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'esm'
}
};
3. Vite
// vite.config.js
export default {
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html')
}
}
}
};
总结
在现代前端开发中,推荐使用 ES Modules 作为主要的模块化方案,配合构建工具实现更好的开发体验和生产优化。