您可能在现代 JavaScript 开发中使用过 ES 模块,但您知道它们演变背后的历史吗?了解从早期 JavaScript 实践到今天的模块系统的历程将帮助您了解我们已经走了多远以及为什么 ES 模块改变了游戏规则。
早期:JavaScript 的起源
时间是 1995 年,第一个网页创建四年后。大多数网站都很简单——带有文本和最小交互性的静态页面。然而,开发人员很快就在寻找使网页更具动态性的方法。
在这种环境下,Netscape(当时占主导地位的网络浏览器)聘请了 Brendan Eich 来创建一种可以直接在浏览器中运行的脚本语言。这导致了 JavaScript 的诞生,这是一种简单易懂的语言,特别是对于像网页设计师这样的非程序员来说。他做到了,在 10 天内完成了第一个版本。
最初,JavaScript 的目的是向网页添加小的增强功能,例如表单验证,而不需要将数据往返到服务器。然而,随着网站的交互性变得越来越强,JavaScript 很快就超出了其最初的用途。
全球范围和早期解决方法
早期,所有 JavaScript 代码都位于全局范围内。随着越来越多的开发人员向同一页面添加代码,名称冲突的风险也随之增加。如果多个脚本使用相同的变量或函数名称,代码可能会以意想不到的方式中断。
为了解决这个问题,开发人员采用命名约定来防止冲突。如果一段代码只运行一次,开发人员通常会将其包装在 IIFE(立即调用函数表达式)中。这使得函数和变量的作用域保持在函数内,防止它们污染全局命名空间。
(function init() { function getData(){ // ... } })()
这在当时已经足够好了,因为大多数网站都是服务器端渲染的,客户端逻辑很少。
CommonJS:服务器端模块
2008 年,Ryan Dahl 创建了 Node.js,这是一个用于构建服务器端应用程序的 JavaScript 运行时。这开辟了一个全新的可能性世界,但缺乏模块系统意味着开发人员难以管理大型代码库。
2009年,CommonJS被引入来解决服务器端的这个问题。 CommonJS 模块系统允许开发人员定义模块、公开功能以及导入其他模块。以下是其工作原理的示例:
const math = require("./math"); function subtract(a,b) { return math.add(a,-b); } module.exports = { subtract: subtract }
使用 CommonJS,每个文件都被视为自己的模块,并且使用 require 函数导入模块并使用 module.exports 导出。
CommonJS 的一些主要功能包括:
需要模块时,文件扩展名是可选的(例如 require('./math') 自动查找 math.js)。
模块加载是同步的,这意味着程序会等待模块加载后再继续执行。
在文章后面我们将了解为什么 Ryan Dahl 承认对这两个设计决定感到遗憾。
AMD:优化浏览器
大约在同一时间,另一个名为AMD(异步模块定义)的模块系统被开发出来。 CommonJS 主要关注服务器端 JavaScript,而 AMD 旨在处理浏览器中的客户端 JavaScript。
AMD 的关键特性是它能够异步加载模块。这使得浏览器可以在任何给定时间仅加载页面所需的 JavaScript,从而通过减少初始页面加载时间来提高性能。它还解决了与依赖项解析相关的问题,确保模块仅在其依赖项完成加载后才会运行。
AMD 的优势包括:
- 按需加载较小的 JavaScript 文件。
- 由于模块加载更加可预测,页面加载错误更少。
- 通过异步加载进行性能优化。
随着 2010 年 npm(服务器端 JavaScript 的包管理器)的兴起,跨浏览器和服务器共享代码的需求变得显而易见。 Browserify 是一个工具,它允许开发人员通过转换代码以与浏览器环境兼容来在浏览器中使用 CommonJS 模块。
UMD:两全其美......有点
有 2 个竞争标准:CommonJS 和 AMD。需要一个可以在任何地方工作而无需构建步骤的单一模块系统。并于 2011 年引入了通用模块定义(UMD)。
UMD 结合了两全其美,允许开发人员编写可以在以下位置运行的模块:
- Node.js(使用 CommonJS)。
- 浏览器(使用 AMD)。
- 全局范围(如果 CommonJS 和 AMD 都不存在)。
UMD 在库作者中非常流行,Lodash、Underscore.js、Backbone.js 和 Moment.js 等著名库都采用了它。然而,UMD 有一些显着的缺点。它在解决兼容性问题的同时,也带来了管理两个系统的复杂性,并且继承了 AMD 和 CommonJS 的问题。
ES 模块:终极模块标准
2015 年,ES 模块 (ESM) 作为 ECMAScript 标准的一部分被引入,最终为 JavaScript 提供了原生模块系统。到 2017 年,所有主流浏览器都支持 ES 模块,2020 年,Node.js 也添加了支持。
让我们看看为什么 ES 模块是最好的:
1. 方便的语法
以下UMD代码:
(function init() { function getData(){ // ... } })()
现在可以简化为:
(function init() { function getData(){ // ... } })()
公平地说,没有人真正这样写 UMD。他们使用 umdify 等工具来生成该代码。但通过内置 ES 模块,我们可以跳过构建步骤并获得更小的包大小。
2.更好的优化
ES 模块是静态的,这意味着工具可以在编译时分析代码结构,以确定哪些代码正在使用,哪些没有。这允许进行树摇动,即从最终包中删除未使用的代码。
由于 CommonJS 和 AMD 模块是动态的(在运行时评估),因此这些系统的 Tree Shaking 效率要低得多,通常会产生更大的包。
3.更显式的代码
使用 CommonJS 导入模块时,指定文件扩展名是可选的。
const math = require("./math"); function subtract(a,b) { return math.add(a,-b); } module.exports = { subtract: subtract }
但是数学到底指的是什么?它是一个 JavaScript 文件吗? JSON 文件?数学目录中的index.js 文件?
当使用 ES Lint、Typescript 或 Prettier 等静态分析工具时,每个需求都变成了猜谜游戏。
是 math.js 吗?
是 math.jsx 吗?
是 math.cjs 吗?
是 math.mjs 吗?
是 math.ts 吗?
是 math.tsx 吗?
是 math.mts 吗?
是 math.cts 吗?
是 math/index.js 吗?
是 math/index.jsx 吗?
你明白了。
读取文件的成本很高。它的性能比从内存中读取要低得多。导入 math/index.js 会导致 9 次 IO 操作,而不是 1 次!这种猜谜游戏正在减慢我们的工具速度并损害开发人员的体验。
在 ES 模块中,我们通过强制文件扩展名来避免这种混乱。
4. 异步加载
与 CommonJS 同步加载模块(阻塞整个进程直到模块加载)不同,ES 模块是异步的。这允许 JavaScript 在后台加载模块时继续执行,从而提高性能 - 特别是在 Node.js 这样的环境中。
迁移挑战:为什么花了这么长时间
尽管有明显的好处,但采用 ES 模块并不是一件简单的任务。这就是过渡花了这么长时间的原因:
1. 昂贵的迁移
从 CommonJS 切换到 ES 模块并不是一个简单的改变,尤其是对于大型项目。语法差异加上对工具支持的需求,使得迁移变得非常困难。
2. 缺乏 Node.js 支持
node.js花了5年时间才完全支持ES模块。在此期间,开发人员必须保持与 CommonJS(在服务器上)和 ES 模块(在浏览器上)的兼容性。这种双重支持在生态系统中造成了很多摩擦。
3、兼容性问题
即使 Node.js 添加了对 ES 模块的支持,CommonJS 模块也无法加载 ES 模块。虽然 ES 模块可以加载 CommonJS 模块,但这两个系统不能完全互操作,这给必须支持这两个系统的包作者带来了额外的麻烦。
未来:ES 模块将继续存在
JavaScript 模块的未来是光明的,以下是一些关键的发展,将使 ES 模块成为未来的主导系统:
1.Node.js 23
在 Node.js 23 中,我们终于能够从 CommonJS 加载 ES 模块。
有一个小警告:使用顶级await的ES模块不能导入到CommonJS中,因为await只能在异步函数中使用,而CommonJS是同步的。
2.JSR(JavaScript注册表)
一个与 npm 竞争的新 javascript 包注册表。它比 npm 有很多优点,我在这里不再赘述。但有趣的是你只能上传 ES 模块包。无需支持旧标准。
结论从全局范围 hack 到现代 ES 模块的旅程改变了我们构造 JavaScript 的方式。经过多年对 CommonJS、AMD 和 UMD 的试验,ES 模块已经成为明确的标准,提供更简单的语法、更好的优化和更高的性能。
虽然迁移到 ES 模块具有挑战性,特别是在 Node.js 支持和生态系统兼容性方面,但好处是不可否认的。随着 Node.js 23 改进互操作性以及 JSR 等新工具促进统一模块系统,ES 模块将成为 JavaScript 的默认模块。
随着我们继续拥抱 ES 模块,我们可以期待更干净、更快、更可维护的代码,标志着 JavaScript 开发模块化的新时代。
以上是ES 模块简史的详细内容。更多信息请关注PHP中文网其他相关文章!

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

记事本++7.3.1
好用且免费的代码编辑器

WebStorm Mac版
好用的JavaScript开发工具

Dreamweaver Mac版
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)