JavaScript模块化开发:CommonJS、AMD到ES模块
引言
在Web开发的早期阶段,JavaScript代码通常被编写在一个庞大的文件中或分散在多个脚本标签里,这种方式导致了全局变量污染、依赖关系难以管理、代码复用困难等问题。随着Web应用日益复杂,模块化编程成为了解决这些问题的关键。本文将带您了解JavaScript模块化的发展历程,从最初的模块模式到CommonJS、AMD,再到现代ES模块,并通过详细的代码示例帮助您掌握每种模块系统的使用方法及其优缺点。
JavaScript模块化的发展历程
1. 早期的模块模式
在模块化标准出现之前,开发者通常使用立即执行函数表达式(IIFE)和闭包来模拟模块的概念:
// 模块模式示例
var Calculator = (function() {// 私有变量var result = 0;// 私有方法function validate(num) {return typeof num === 'number';}// 公共APIreturn {add: function(num) {if (validate(num)) {result += num;}return this;},subtract: function(num) {if (validate(num)) {result -= num;}return this;},getResult: function() {return result;}};
})();// 使用模块
Calculator.add(5).subtract(2);
console.log(Calculator.getResult()); // 输出: 3
代码解析:
- 使用IIFE创建一个闭包环境
result变量和validate方法是模块的私有成员,外部无法访问- 返回一个包含公共方法的对象,形成模块的公共API
- 实现了基本的封装,但不支持依赖管理
2. CommonJS规范
CommonJS最初是为服务器端JavaScript设计的模块规范,后来通过Browserify、Webpack等工具被引入到浏览器环境。Node.js采用的就是这一规范:
// math.js - 定义模块
// 私有变量
const PI = 3.14159;// 私有函数
function square(x) {return x * x;
}// 导出公共API
exports.area = function(radius) {return PI * square(radius);
};exports.circumference = function(radius) {return 2 * PI * radius;
};// 或者使用module.exports整体导出
module.exports = {area: function(radius) {return PI * square(radius);},circumference: function(radius) {return 2 * PI * radius;}
};
// app.js - 使用模块
const math = require('./math.js');console.log(`圆面积: ${math.area(5)}`); // 输出: 圆面积: 78.53975
console.log(`圆周长: ${math.circumference(5)}`); // 输出: 圆周长: 31.4159
代码解析:
require()函数用于导入模块exports对象或module.exports用于导出模块接口- 模块在第一次被
require时执行一次,之后缓存结果 - 导入的是值的拷贝,而非引用(这与ES模块不同)
- 同步加载模式,适合服务器环境
3. AMD规范(Asynchronous Module Definition)
AMD规范专为浏览器环境设计,支持异步加载模块,最著名的实现是RequireJS:
// 定义一个名为'calculator'的模块,依赖于'math'模块
define('calculator', ['math'], function(math) {// 私有变量var result = 0;// 返回模块公共APIreturn {add: function(num) {result += num;return this;},subtract: function(num) {result -= num;return this;},multiply: function(num) {result = math.multiply(result, num);return this;},getResult: function() {return result;}};
});// math.js - 依赖模块
define('math', [], function() {return {add: function(a, b) { return a + b; },subtract: function(a, b) { return a - b; },multiply: function(a, b) { return a * b; },divide: function(a, b) { return a / b; }};
});// 使用模块
require(['calculator'], function(calculator) {calculator.add(10).multiply(2);console.log(calculator.getResult()); // 输出: 20
});
代码解析:
define()函数用于定义模块,指定模块ID、依赖数组和工厂函数require()函数用于加载模块并执行回调- 支持异步加载,适合浏览器环境
- 依赖前置声明,便于优化和并行加载
4. UMD(Universal Module Definition)
UMD是一种兼容CommonJS和AMD的模式,同时支持浏览器全局变量:
// UMD模式 - 兼容CommonJS、AMD和全局变量
(function(root, factory) {if (typeof define === 'function' && define.amd) {// AMDdefine(['jquery'], factory);} else if (typeof module === 'object' && module.exports) {// CommonJSmodule.exports = factory(require('jquery'));} else {// 浏览器全局变量root.MyModule = factory(root.jQuery);}
}(typeof self !== 'undefined' ? self : this, function($) {// 模块代码var MyModule = {init: function() {$('.element').on('click', this.handleClick);},handleClick: function() {console.log('Element clicked');}};return MyModule;
}));
代码解析:
- 通过检测环境判断使用哪种模块系统
- 适合需要跨环境运行的库
- 代码较为复杂,不太适合应用开发
5. ES模块(ECMAScript Modules)
ES模块是JavaScript的官方标准模块系统,已被所有现代浏览器原生支持:
// math.js - ES模块
// 私有变量和函数(模块作用域内)
const PI = 3.14159;function square(x) {return x * x;
}// 命名导出
export function area(radius) {return PI * square(radius);
}export function circumference(radius) {return 2 * PI * radius;
}// 默认导出
export default {area,circumference
};
// app.js - 导入ES模块
// 导入命名导出
import { area, circumference } from './math.js';
console.log(`圆面积: ${area(5)}`); // 输出: 圆面积: 78.53975
console.log(`圆周长: ${circumference(5)}`); // 输出: 圆周长: 31.4159// 导入默认导出
import Math from './math.js';
console.log(`圆面积: ${Math.area(5)}`); // 输出: 圆面积: 78.53975// 导入所有并重命名
import * as MathUtils from './math.js';
console.log(`圆面积: ${MathUtils.area(5)}`); // 输出: 圆面积: 78.53975// 动态导入
async function loadMath() {const mathModule = await import('./math.js');console.log(`圆面积: ${mathModule.area(5)}`);
}
loadMath();
代码解析:
- 使用
export关键字导出模块接口 - 使用
import关键字导入模块 - 支持命名导出、默认导出和命名空间导入
- 静态分析,编译时确定依赖关系
- 导入的是绑定(引用),而非值的拷贝
- 支持动态导入(
import()函数) - 模块自动运行在严格模式下
- 模块作用域隔离,顶级声明不会污染全局作用域
使用建议与最佳实践
1. 选择合适的模块系统
- 服务器端开发:使用CommonJS(Node.js原生支持)或ES模块(Node.js 12+支持)
- 现代浏览器应用:首选ES模块(无需转译)
- 需要兼容旧浏览器:使用ES模块 + Webpack/Rollup等构建工具转译
- 开发通用库:考虑使用UMD格式发布,以支持各种环境
2. ES模块最佳实践
- 保持模块功能单一,一个模块只负责一个功能
- 避免循环依赖,可能导致未初始化变量的使用
- 使用命名导出而非默认导出,便于静态分析和IDE自动补全
- 按需导入,减少不必要的代码加载
- 使用路径别名,简化深层模块的导入路径
- 使用动态导入实现代码分割和按需加载
- 考虑使用构建工具,处理兼容性和优化打包
3. 常见问题与注意事项
-
浏览器对ES模块的CORS要求:ES模块必须通过服务器提供,不能通过
file://协议加载 -
模块缓存:模块在首次导入时执行,之后从缓存中获取,需谨慎使用模块级变量
-
使用
nomodule属性为旧浏览器提供备选方案:<script type="module" src="app.js"></script> <script nomodule src="app-legacy.js"></script> -
构建工具配置:正确配置Webpack、Rollup等工具以处理模块依赖
-
Node.js中的ES模块:使用
.mjs扩展名或在package.json中设置"type": "module"
总结
JavaScript模块化发展历程从早期的模块模式、CommonJS、AMD到现代ES模块,反映了Web开发复杂性不断提高的需求。如今,ES模块已成为JavaScript生态系统的标准部分,被现代浏览器和Node.js原生支持。
模块化开发带来的主要优势包括:
- 代码组织:将功能分解为独立、可管理的块
- 封装:隐藏实现细节,只暴露必要的接口
- 依赖管理:明确模块间的依赖关系
- 可重用性:模块可在不同项目中重复使用
- 可维护性:独立模块易于测试和维护
- 按需加载:支持懒加载和代码分割
随着Web应用变得越来越复杂,掌握模块化开发已经成为前端开发者的基本技能。无论是使用原生ES模块,还是结合Webpack、Rollup等构建工具,模块化思想都将帮助您构建更加可维护、可扩展的应用程序。通过采用本文介绍的最佳实践,您可以充分发挥模块化开发的优势,提升代码质量和开发效率。
在实际项目中,根据具体需求选择适合的模块系统,并遵循相应的最佳实践,将使您的开发工作事半功倍。
相关文章:
JavaScript模块化开发:CommonJS、AMD到ES模块
引言 在Web开发的早期阶段,JavaScript代码通常被编写在一个庞大的文件中或分散在多个脚本标签里,这种方式导致了全局变量污染、依赖关系难以管理、代码复用困难等问题。随着Web应用日益复杂,模块化编程成为了解决这些问题的关键。本文将带您…...
【k8s系列1】一主两从结构的环境准备
环境准备 虚拟机软件准备及安装,这里就不详细展开了,可以看文章:【一、虚拟机vmware安装】 linux环境准备及下载,下载镜像centOS7.9,以前也有写过这个步骤的文章,可以看:【二、安装centOS】 开始进入正题…...
dubbo SPI插件扩展点使用
参考:SPI插件扩展点 Dubbo SPI概述 使用IoC容器帮助管理组件的生命周期、依赖关系注入等是很多开发框架的常用设计,Dubbo中内置了一个轻量版本的IoC容器,用来管理框架内部的插件,实现包括插件实例化、生命周期、依赖关系自动注入…...
青少年编程与数学 02-016 Python数据结构与算法 26课题、生物信息学算法
青少年编程与数学 02-016 Python数据结构与算法 26课题、生物信息学算法 一、序列比对算法二、基因表达分析算法三、蛋白质结构预测算法四、系统生物学模型构建算法五、单细胞分析算法六、遗传关联分析算法七、机器学习与数据挖掘算法八、数据可视化算法九、代谢组学分析算法十…...
【Rust 精进之路之第2篇-初体验】安装、配置与 Hello Cargo:踏出 Rust 开发第一步
系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 **作者:**码觉客 发布日期: 2025-04-20 引言:磨刀不误砍柴工,装备先行! 在上一篇文章中,我们一起探索了 Rust 诞生的缘由&…...
洛谷题目:P7775 [COCI 2009/2010 #2] VUK 题解 (本题简)
题目传送门: P7775 [COCI 2009/2010 #2] VUK - 洛谷 (luogu.com.cn) 前言: 这道题的核心目标是找出狼从起点 V 到终点 J 的路径,使得狼在途中离它最近的树的距离的最小值最大。下面为大家详细讲解: #整体思路概述: 这道题我们可以采用“先计算距离,再来二分查找”的…...
腾讯旗下InstantCharacter框架正式开源 可高度个性化任何角色
目前基于学习的主题定制方法主要依赖于 U-Net 架构,但其泛化能力有限,图像质量也大打折扣。同时,基于优化的方法需要针对特定主题进行微调,这不可避免地会降低文本的可控性。为了应对这些挑战,我们提出了 “即时角色”…...
Python爬虫实战:获取fenbi网最新备考资讯
一、引言 1.1 研究背景 伴随互联网技术的迅猛发展,在线教育平台积累了海量备考数据。以粉某网为例,其备考数据涵盖考试资讯、备考资料、用户评价等,对备考者意义重大。然而,获取并分析这些数据颇具挑战,需借助先进的爬虫技术和数据分析方法。 1.2 研究目的 本研究旨在…...
详讲Linux下进程等待
3.进程等待 引言:什么是进程等待 想象有两个小伙伴,一个是 “大强”(父进程 ),一个是 “小强”(子进程 )。大强给小强安排了任务,比如去收集一些石头。 …...
JBoss + WildFly 本地开发环境完全指南
JBoss WildFly 本地开发环境完全指南 本篇笔记主要实现在本地通过 docker 创建 JBoss 和 WildFly 服务器这一功能,基于红帽的禁制 EAP 版本的重新分发,所以我这里没办法放 JBoss EAP 的 zip 文件。WildFly 是免费开源的版本,可以在红帽官网找…...
【网络原理】TCP协议如何实现可靠传输(确认应答和超时重传机制)
目录 一. TCP协议 二. 确定应答 三. 超时重传 一. TCP协议 1)端口号 源端口号:发送方端口号目的端口号:接收方端口号 16位(2字节)端口号,可以表示的范围(0~65535) 源端口和目的…...
【国家能源集团生态协作平台-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
Java表达式1.0
Java开发工具 在当今的Java开发领域,IntelliJ IDEA已然成为了众多开发者心目中的首选利器,它被广泛认为是目前Java开发效率最快的IDE工具。这款备受瞩目的开发工具是由JetBrains公司精心打造的,而JetBrains公司总部位于风景如画的捷克共和国首…...
idea中导入从GitHub上克隆下来的springboot项目解决找不到主类的问题
第一步:删除目录下的.idea和target,然后用idea打开 第二步:如果有需要,idea更换jdk版本 原文链接:https://blog.csdn.net/m0_74036731/article/details/146779040 解决方法(idea中解决)&#…...
Android音视频开发
Android Framework 与音视频技术深度解析 一、Android音视频架构全景 ▶ 四层架构协同┌──────────────┐│ 应用层 │ ▶ MediaPlayer/ExoPlayer/Camera2 API调用└──────┬───────┘┌──────▼───────┐│ 框架层 │…...
【AI论文】CLIMB:基于聚类的迭代数据混合自举语言模型预训练
摘要:预训练数据集通常是从网络内容中收集的,缺乏固有的领域划分。 例如,像 Common Crawl 这样广泛使用的数据集并不包含明确的领域标签,而手动整理标记数据集(如 The Pile)则是一项劳动密集型工作。 因此&…...
Linux操作系统--环境变量
目录 基本概念: 常见环境变量: 查看环境变量的方法: 测试PATH 测试HOME 和环境变量相关的命令 环境变量的组织方式:编辑 通过代码如何获取环境变量 通过系统调用获取或设置环境变量 环境变量通常具有全局属性 基本概念…...
Jenkins 多分支管道
如果您正在寻找一个基于拉取请求或分支的自动化 Jenkins 持续集成和交付 (CI/CD) 流水线,本指南将帮助您全面了解如何使用 Jenkins 多分支流水线实现它。 Jenkins 的多分支流水线是设计 CI/CD 工作流的最佳方式之一,因为它完全基于 git(源代…...
精益数据分析(9/126):如何筛选创业路上的关键数据指标
精益数据分析(9/126):如何筛选创业路上的关键数据指标 大家好!在创业的漫漫长路中,数据就像一盏明灯,指引着我们前行的方向。但要让这盏灯发挥作用,关键在于找到那些真正有价值的数据指标。今天…...
C语言之图像文件的属性
🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 图像文件属性提取系统设计与实现 目录 设计题目设计内容系统分析总体设计详细设计程序实现…...
LeetCode hot 100—分割等和子集
题目 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 示例 1: 输入:nums [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。…...
高等数学同步测试卷 同济7版 试卷部分 上 做题记录 上册期中同步测试卷 B卷
上册期中同步测试卷 B卷 一、单项选择题(本大题共5小题,每小题3分,总计15分) 1. 2. 3. 4. 5. 由f(2/n), n→∞可知 2/n→0, 即x→0. 二、填空题(本大题共5小题,每小题3分,总计15分) 6. 7. 8. 9. 10. 三、求解下列各题(本大题共5小…...
【算法】快速排序、归并排序(非递归版)
目录 一、快速排序(非递归) 1.原理 2.实现 2.1 stack 2.2 partition(array,left,right) 2.3 pivot - 1 > left 二、归并排序(非递归) 1.原理 2.实现 2.1 gap 2.1.1 i 2*gap 2.1.2 gap * 2 2.1.3 gap < array.…...
python-将文本生成音频
将文本生成音频通常需要结合 文本转语音(TTS,Text-to-Speech) 工具或库来实现,比如 Google TTS (gtts)、Amazon Polly、Microsoft Azure TTS 等。 一、使用 Google TTS (gtts) 将文本生成音频 gtts 是一个简单易用的 Python 库&a…...
[王阳明代数讲义]语言模型核心代码调研
语言模型核心代码调研 基于Consciciteation的才气张量持续思考综述将文本生成建模为才气张量网络扩散过程,实现非自回归推理通过才气张量的群-拓扑流形交叉注意力实现多模态推理,将输入压缩到低维空间持续迭代提出「条件计算提前终止」机制,…...
4月19日记(补)算了和周日一块写了 4月20日日记
周六啊 昨天晚上又玩的太嗨了。睡觉的时候有点晚了,眼睛疼就没写日记。现在补上 实际上现在是20号晚上八点半了。理论上来说应该写今天的日记。 周六上午打比赛啦,和研究生,输了,我是替补没上场。没关系再练一练明天就可以变强…...
trivy开源安全漏洞扫描器——筑梦之路
开源地址:https://github.com/aquasecurity/trivy.git 可扫描的对象 容器镜像文件系统Git存储库(远程)虚拟机镜像Kubernetes 在容器镜像安全方面使用广泛,其他使用相对较少。 能够发现的问题 正在使用的操作系统包和软件依赖项…...
【实战中提升自己】内网安全部署之dot1x部署 本地与集成AD域的主流方式(附带MAC认证)
1 dot1x部署【用户名密码认证,也可以解决私接无线AP等功能】 说明:如果一个网络需要通过用户名认证才能访问内网,而认证失败只能访问外网与服务器,可以部署dot1x功能。它能实现的效果是,当内部用户输入正常的…...
[matlab]南海地形眩晕图代码
[matlab]南海地形眩晕图代码 请ChatGPT帮写个南海地形眩晕图代码 图片 图片 代码 .rtcContent { padding: 30px; } .lineNode {font-size: 12pt; font-family: "Times New Roman", Menlo, Monaco, Consolas, "Courier New", monospace; font-style: n…...
Web安全和渗透测试--day6--sql注入--part 1
场景: win11家庭版,edge浏览器 , sqlin靶场 定义: SQL 注入(SQL Injection)是一种常见的网络安全攻击方式,攻击者通过在 Web 应用程序中输入恶意的 SQL 代码,绕过应用程序的安全机…...
