另一个 JavaScript 框架

Avatar of Jay Hoffmann
Jay Hoffmann

DigitalOcean 为您的旅程各个阶段提供云产品。立即开始使用 $200 免费信用额度!

2018 年 3 月 6 日,添加了一个新错误到官方的 Mozilla Firefox 浏览器错误跟踪器。一位开发人员注意到 Mozilla 的夜间构建版本中存在一个问题。报告指出,德国网站上通常显示的 14 天天气预报小部件突然崩溃并消失。网站上没有任何更改,因此问题必须出在 Firefox 上。

Bug 1443630: Implementing array.prototype.flatten broke MooTools' version of it.
提交给 Mozilla 的错误报告截图。

开发人员在他的报告中指出,该问题似乎源于该网站使用 JavaScript 库 MooTools。

乍一看,这个错误似乎相当平常,很可能是网站代码中的一个小问题或一个奇怪的巧合。然而,仅仅几个小时后,人们意识到,这个特定错误的影响远比任何人预料的都要严重。如果 Firefox 按原样发布这个版本的浏览器,他们就有可能一次性破坏大量未知但可以预见的是相当庞大的网站。为什么会出现这种情况,这与 MooTools 的构建方式、它借鉴的影响和它发布的时间节点息息相关。因此,要真正理解这个问题,我们必须回到最初。

最初

首先出现的是纯 JavaScript。1995 年,Netscape 的一个团队发布了 JavaScript,它在 90 年代后期开始被广泛使用。JavaScript 为使用 HTML 的 Web 开发人员提供了助力,使他们能够动态地调整内容布局,进行简单的动画效果,并为网站添加计数器、股票行情、天气小部件和其他各种交互功能。

到 2005 年,JavaScript 开发变得越来越复杂。这得益于一种名为 异步 JavaScript 和 XML (Ajax) 的技术的使用,这种模式在当今任何使用网站进行不仅仅是阅读内容的操作的人看来都熟悉。Ajax 为 Web 上的应用程序式功能打开了大门,使得像 Google 地图和 Gmail 这样的项目得以发布。为了描述这种动态、面向用户和交互式 Web 开发的新时代,人们开始随意地谈论 “Web 2.0”。这一切都要归功于 JavaScript。

在世纪之交的最初几年,正是 Ajax 吸引了 Sam Stephenson 的反复关注。Stephenson 是 Ruby on Rails 的常客,在尝试使用一些相当常见的 Ajax 代码将 JavaScript 连接到 Rails 时,他总是遇到同样的问题。具体来说,他在每次开始新项目时都编写相同的基线代码。因此,他编写了几百行代码来简化 Ajax 与 Rails 之间的连接,这些代码可以移植到他的所有项目中。仅仅几个月的时间,一百行代码变成了更多行,Prototype,第一个完整的 JavaScript 框架的早期示例,正式发布了。

A screenshot of the Prototype website.
Prototype 网站的早期版本,强调其易用性和基于类的结构。

扩展 JavaScript

Ruby 使用类继承,这有利于面向对象开发。如果你不知道这意味着什么,你只需要知道它与 JavaScript 的构建方式有点冲突。JavaScript 依赖于称为原型继承的东西。这意味着 JavaScript 中的所有东西都可以使用基本对象作为原型进行扩展。任何东西。甚至像StringArray这样的原生对象原型。事实上,当浏览器向 Javascript 添加新函数和功能时,它们通常会利用这个特定的语言特性。这就是 Stephenson 为他的库取名为Prototype的原因。

底线是,原型继承使 JavaScript 天然地具有容错性和可扩展性。任何开发人员基本上都可以根据自己的代码构建在核心 JavaScript 库之上。在许多其他编程语言中,这不可能做到,但 JavaScript 在其方法方面一直是一个例外,以适应更广泛、跨领域的开发者群体。

Stephenson 在编写 Prototype 时做了两件事。第一件事是添加了一些辅助工具,使像他这样的面向对象开发人员能够使用熟悉的代码结构来使用 JavaScript。第二件事,也是更重要的一件事,是开始扩展现有的 Javascript,添加一些将来计划实现但尚未实现的功能。一个很好的例子是document.getElementByClassName函数,它是一个稍微重新命名的版本,直到大约 2008 年才真正出现在 JavaScript 中。Prototype 让你早在 2006 年就能使用它。这个库基本上是一个愿望清单,列出了开发者期望浏览器在未来的某个时间点实现的功能。Prototype 为这些开发人员提供了先机,并使他们更容易完成每天都需要完成的简单任务。

Prototype 经历了几个快速迭代,在发布后不久被默认包含在所有 Ruby on Rails 安装中,从而获得了巨大的发展势头。在此过程中,Prototype 为之后出现的几乎所有框架奠定了基础。例如,它是第一个使用美元符号 ($) 作为 JavaScript 中选择对象的简写符号的框架。它编写的代码用它自己的话说,是 “自文档的”,这意味着文档很少,学习该库意味着深入研究一些代码,这种做法在今天已变得相当普遍。也许最重要的是,它消除了使代码在所有浏览器上运行的难度,这在浏览器本身对很少东西能够达成一致的时代是一项几乎无法完成的任务。Prototype 可以在当时所有现代浏览器上运行。

Prototype 与其他库(如 base2)竞争激烈,这些库采用了 Prototype 的面向对象部分,并将它们剥离成更紧凑的版本。但是,这个库最大的竞争对手出现在 John Resig 决定加入这场竞赛时。Resig 对最后一点特别感兴趣,即 “在所有浏览器上使用相同的代码运行” 这部分。他从 2005 年开始着手对这个想法进行不同的尝试,并在 2006 年 1 月 最终在纽约的 Barcamp 上发布了自己的库

它被称为 jQuery。

新兴 JavaScript

jQuery 推出了 “新兴 JavaScript” 的口号,考虑到 Resig 从 Prototype 中借鉴了多少东西,这是一个奇怪的标题。从语法到用于处理 Ajax 的工具,甚至使用美元符号作为选择器,都从 Prototype 传到了 jQuery。但它之所以被称为 “新兴 JavaScript”,并不是因为它独创。之所以被称为 “新兴 JavaScript”,是因为它很新。

jQuery: New Wave JavaScript. jQuery is a new type of JavaScript library. jQuery is designed to change the way that you write JavaScript.
“新兴 JavaScript”

jQuery 与 Prototype 及其同类产品最大的区别在于,它没有扩展现有和未来的 JavaScript 功能或对象原语。相反,它创建了全新的功能,所有这些功能都是使用一个独特的 API 构建的,该 API 构建在 JavaScript 中已经存在的东西之上。与 Prototype 一样,jQuery 提供了大量与网页交互的方式,比如选择和移动元素,连接到服务器,使页面感觉快速而动态(尽管它缺乏其前身的面向对象倾向)。但最重要的是,所有这些都是通过代码实现的。新的函数、新的语法、新的 API,因此出现了新的开发浪潮。你需要学习 “jQuery 式” 的做事方式,但一旦你学会了,你就可以通过 jQuery 使许多事情变得更容易,从而节省大量时间。它甚至允许扩展插件,这意味着其他开发人员可以在其基础上构建酷炫的新功能。

MooTools

这听起来可能很小,但这种细微的范式转变实际上非常巨大。如此重大的转变需要做出回应,而这种回应恰好出现在下一年,即 2007 年,当时 Valerio Proietti 发现自己对另一个库完全感到沮丧。这个库名为 script.aculo.us,它帮助开发人员进行页面过渡和动画。Proietti 尽了最大的努力,但就是无法让 script.aculo.us 做到他想要做的事情,因此 (正如历史上许多处于他这种境地的开发人员所做的那样),他决定重写自己的版本。他自己也是一个面向对象的开发人员,他已经是 Prototype 的忠实粉丝,因此他将第一个版本建立在该库的基础原则之上。他甚至试图借助他的第一个名称来借鉴 Prototype 的成功:prototype.lite.js。几个月后,经过了许多新功能的添加,Proietti 将其改造成了 MooTools。

与 Prototype 类似,MooTools 使用面向对象编程方法和原型继承来扩展核心 JavaScript 的功能。事实上,MooTools 的大部分内容只是在内置原型(如 StringArray)上添加新功能。MooTools 添加的大部分内容都在 JavaScript 路线图中,计划在未来的某个时间点包含在浏览器中;该库只是为了在此时填补空白。总体思路是,一旦某个特性最终登陆浏览器,他们就会简单地更新自己的代码以匹配它。网页设计师和开发者喜欢 MooTools,因为它功能强大,易于使用,让他们感觉自己是在未来编码。

MooTools is a compact, modular, Object-oriented JavaScript framework designed to make writing extensible and compatible code easier and faster.
MooTools:面向对象,开发者友好

jQuery 也提供了很多功能。与 jQuery 一样,MooTools 弥合了市面上各种浏览器的差异和 bug,并提供了快速简便的方法来添加过渡效果,进行服务器请求以及动态操作网页。到 MooTools 发布时,jQuery 语法已经或多或少成为标准,而 MooTools 不会成为打破这种模式的人。

事实上,两者之间有足够的相似之处,以至于在几乎无休止的博客文章和思考文章中,它们被相互比较。MooTools 与 jQuery,一个永恒的问题。 网站纷纷涌现 来比较两者。MooTools 是一个“框架”,jQuery 是一个“库”。MooTools 使编码变得有趣,jQuery 使网络变得有趣。MooTools 适用于双子座,而 jQuery 适用于射手座。实际上,两者都运行良好,使用哪一个主要取决于个人喜好。这在很大程度上适用于许多最常见的开发库争论,但它们仍然在继续。

传统框架的遗产

最终,决定胜负的不是功能或代码结构,而是时间。MooTools 的核心贡献者一个接一个地从项目中退出,转而从事其他工作。到 2010 年,只剩下少数几个人。开发速度放缓,社区也随之落后。MooTools 仍然很受欢迎,但它的势头已经戛然而止。

jQuery 的优势在于他们不断前进,甚至不断扩展。在 2010 年,MooTools 开发开始结束时,jQuery 发布了第一个版本的 jQuery Mobile,尝试将该库重新调整以适应移动世界。jQuery 社区从未放弃,最终,这给了他们优势。

然而,MooTools 的遗产和影响力是巨大的。它出现在数十万个网站上,并传播到世界各地。根据 我们拥有的某些统计数据,即使在今天,在普通网站上看到 MooTools 的频率仍然比看到 Angular、React、Vue 或任何现代框架都要高。一些网站的代码已经更新,以跟上 MooTools 的更新速度,尽管更新频率较低,但仍然偶尔更新。其他网站至今仍对他们安装的 MooTools 版本感到满意。大多数网站根本没有更新。当网站构建时,MooTools 是当时最好的选择,现在,几年过去了,同一个版本仍然存在。

Array.flatten

这让我们回到了 2018 年初在 Firefox 中出现的那个天气应用程序的 bug。请记住,MooTools 是以 Prototype 为模型的。它修改了原生 JavaScript 原型对象,以添加一些计划但尚未发布的功能。在本例中,这是一个名为 Array.flatten 的方法,该方法是 MooTools 在 2008 年首次添加到其库中的,用于修改数组。快进十年,JavaScript 工作组终于开始实现他们自己的 Array.flatten 版本,从 Firefox 的 beta 版开始。

问题是 Firefox 的 Array.flatten 没有直接映射到 MooTools 版本的 Array.flatten

细节并不重要 (虽然你可以在此处阅读更多相关信息)。更关键的是令人不安的含义。MooTools 版本在与新 JavaScript 版本发生冲突时,会崩溃。这就是天气小部件崩溃的原因。如果 Firefox 将其浏览器发布到更广泛的公众,那么 MooTools 版本的 flatten 将抛出错误,并清除任何依赖它的 JavaScript。没有人能说有多少网站可能会受到冲突的影响,但考虑到 MooTools 的流行程度,完全有可能认为破坏的程度可能很严重。

一旦 bug 出现,JavaScript 社区便进行了紧急讨论,其中很大一部分是在 JavaScript 工作组的 公共 GitHub 库 中进行的。很快,一些解决方案出现了。第一个解决方案是简单地发布新版本的 flatten。从本质上来说,就是让旧网站崩溃。有人争论说,这个提议有一种简单的优雅,它基于这样一个根本理念:浏览器有责任推动网络发展。破坏网站将迫使网站所有者升级,我们最终可以摆脱旧的和过时的 MooTools 版本。

其他人很快跳出来指出,网络几乎是无限的,不可能跟踪哪些网站可能会受到影响。这些网站中的很大一部分可能已经好几年没有更新了。有些网站可能已经被放弃了。还有一些网站可能没有资源升级。我们应该让这些网站烂掉吗?安全、可原谅的方法是重新调整该函数,使其向后兼容或完全兼容 MooTools。发布后,即使 Array.flatten 的最终实现不尽如人意,也不会出现任何崩溃情况。

在两者之间,一个最终的提议表明,最好的行动方案可能是完全重命名该函数,从本质上来说,完全避开这个问题,避免两个实现需要相互配合。

一位开发者建议使用 Array.smoosh 作为名称,最终导致整个事件被称为 **Smooshgate**,这很不幸,因为它掩盖了更有趣的问题,即关于网络本质的争论。它揭示了一个关于浏览器制造商和开发者在为网络的每个用户和每个构建者提供一个可访问、开放和宽容的体验方面的责任的根本问题,即使 (也许尤其是) 当网络的标准被完全忽略时。简而言之,这个问题是,_我们是否应该破坏网络?_

需要明确的是,网络是一个无处不在且快速发展的媒介,最初是为了共享文本和链接而构建的,而且除此之外几乎没有其他用途,但现在每天被数十亿人用于生活的方方面面,做着真正非凡的事情。它偶尔会自行崩溃。但是,当出现一个完全暴露在眼前且最终可以预防的情况时,正确的行动方案是努力推动网络发展,还是确保网络以其当前的形式继续运行,即使技术在进步?

这只会引出更多问题。谁应该负责做出这些决定?是否应该以某种方式对每个库进行积极维护,永无止境,即使最佳实践转变为反模式?作为开发者,我们对那些我们知道已经被放弃的网站有什么义务?最重要的是,我们如何在最好地服务网络的许多不同用户的同时,仍然为开发者提供新的编程工具?这些都是我们不断回到的问题,并且一直是诸如渐进增强、响应式设计和可访问性等讨论的核心。

我们现在该何去何从?

不可能简单地回答所有这些问题。但是,它们可以用网络本身的意识形态项目来框架。网络是为了开放而构建的,既在技术上作为去中心化的网络,又在哲学上作为一种民主化的媒介。这些问题很棘手,因为网络不属于任何人,但它是为每个人而构建的。保持这种精神需要付出很多努力,并且需要在网络技术发展轨迹方面做出一些缓慢但始终慎重的决定。我们应该谨慎地考虑可能在整个网络存在期间留下的海量遗留代码和库。不仅因为它们通常是出于最好的意愿构建的,而且因为许多遗留代码和库已经融入网络的结构中。如果我们用力拉动任何一根线,我们就有可能解开整个网络。

随着 JavaScript 工作组朝着解决方法前进,这些问题中的许多都以这样或那样的形式浮出水面。最终,解决方案是一种折衷方案。Array.flatten 被重命名为 Array.flat,现在已在大多数现代浏览器版本中可用。很难说这是否是绝对最好的决定,当然,我们不会总能把事情做对。但是,如果我们记住网络的基础理念——它作为一种可访问、包容且不断变化的媒介而构建的,并将其作为指导——那么它可以帮助我们的决策过程。这似乎是 Smooshgate 案件的核心。

有一天,你可能在浏览网页时偶然发现一个已经好几年没有更新的旧网站。在顶部,你甚至可能注意到一个告诉你天气的小部件。它会继续运行,因为 JavaScript 选择了弯曲而不是断裂。

你喜欢学习像这样的网络历史故事吗?Jay Hoffmann 正在 The History of the Web 上讲述网络的完整故事,从一开始一直到今天。注册他的时事通讯以了解最新的…过去的事情!