JavaScript 社区由一个库引发的“smoosh门”事件到底怎么回事?
原文: https://developers.google.com/web/updates/2018/03/smooshgate
作者:Mathias Bynens
0. smoosh?!发生了什么?!
一项名为 JavaScript 功能的提案 Array.prototype.flatten
证明与 Web 不兼容。在 Firefox Nightly 中发布该功能会导致至少一个受欢迎的网站中断。鉴于有问题的代码是广泛使用的 MooTools 库的一部分,很可能会有更多网站受到影响。(尽管 MooTools 在 2018 年并不常用于新网站,但它曾经非常流行,并且仍然存在于许多已经正在运行的网站上。)
该提案笔者开玩笑地建议把 flatten
重命名为 smoosh
,以避免兼容性问题。
但是,并非所有人都知道这是一个笑话,有些人开始错误地认为这个新名字已经被确定,并且事情迅速升级。
1. Array.prototype.flatten
是什么?
Array.prototype.flatten
递归地将数组展按照指定的 depth
进行展平, depth
的默认值为 1
。
// Flatten one level:
const array = [1, [2, [3]]];
array.flatten();
// → [1, 2, [3]]
// Flatten recursively until the array contains no more nested arrays:
array.flatten(Infinity);
// → [1, 2, 3]
同样的提议还包括 Array.prototype.flatMap
,如同 Array.prototype.map
一样,可以在参数里面传递一个回调函数。
[2, 3, 4].flatMap((x) => [x, x * 2]);
// → [2, 4, 3, 6, 4, 8]
2. MooTools 导致了什么问题?
MooTools 定义了他们自己的非标准版本 Array.prototype.flatten
:
Array.prototype.flatten = /* non-standard implementation */;
MooTools 的 flatten
实现与建议的标准不同。但是,这并不是问题!当浏览器提供了原生的 Array.prototype.flatten
时,MooTools 会覆盖原生实现。这可确保依赖 MooTools 的代码按预期运行,无论原生 flatten
是否可用。到现在为止还挺好!
不幸的是,发生了其他事情。MooTools 将其所有自定义数组方法复制到 Elements.prototype
( Elements
是 MooTools 特定的 API):
for (var key in Array.prototype) {
Elements.prototype[key] = Array.prototype[key];
}
for
- in
遍历“可枚举”属性,其中不包括像原生方法 Array.prototype.sort
,而是包括自定义的属性 Array.prototype.foo=whatever
。但是 - 背锅开始了 - 如果你覆盖了一个非枚举属性,例如 Array.prototype.sort=whatever
,那么这个属性仍然是不可枚举的。
目前, Array.prototype.flatten=mooToolsFlattenImplementation
创建一个枚举 flatten
属性,所以它以后会被复制到 Elements
。但是,如果我们发布原生版本的 flatten
,它将变得不可枚举,并且不会被复制到 Elements
。现在,任何使用 MooTools 并依赖于 Elements.prototype.flatten
的代码都被破坏了。
尽管将原生 Array.prototype.flatten
变为可枚举可能会解决问题,但它可能会导致更多的兼容性问题。每个依赖于 for
- in
遍历数组(这是一个糟糕的做法,但它经常被使用)的网站会突然得到该 flatten
属性的循环迭代。
这里更大的底层问题是修改内置对象。现在扩展本地原型通常被认为是一种不好的做法,因为它不能很好地与其他库和第三方代码结合。不要修改不属于你的对象!
3. 我们为什么不保留现有名称并打破网络?
1996 年,在 CSS 广泛传播之前,早在“HTML5”之前,Space Jam 网站就已经开始运行了。今天,该网站已经顺利运行 22年了。
这是怎么做到的呢?这些年有没有人维护该网站,每次浏览器供应商发布新功能时都会更新它?
事实证明,“不要打破网络”是 HTML,CSS,JavaScript 和 Web 任何标准上都广泛使用的头号设计原则。如果发布新的浏览器功能导致现有网站停止工作,那对每个人都不利:
受影响网站的访问者突然得到一个破坏的用户体验;
网站所有者从一个完美的网站变成了一个没有功能的网站,而网站所有者却并没有改变任何东西;
用户看到“只支持 XXX 浏览器”之后切换浏览器,因此推出新功能的浏览器供应商失去了市场份额。
一旦知道兼容性问题,其他浏览器供应商拒绝实现此特性。导致某特性的规范与实际实现情况不符(“只是虚构的作品”),这对标准化过程不利。
当然,回想起来 MooTools 做错了一件事 - 但是打破网络并不惩罚它们(MooTools),而是会惩罚用户。这些用户不知道 MooTools 是什么。
或者,我们可以找到另一种解决方案,用户可以继续使用网络。
4. 这是否意味着无法从 Web 平台中删除不好的 API?
在极少数情况下,可以从网络中删除不良的功能。即使仅仅弄清楚是否可以删除一个功能也是非常棘手的工作,需要大量的遥测来量化有多少网页会改变他们的行为。但是,如果功能足够不安全,对用户有害,或者很少使用,则可以完成此操作。
<applet>
, <keygen>
和 showModalDialog()
都是从 Web 平台成功删除的错误 API 的示例。
5. 为什么不修复 MooTools?
修补 MooTools 以便它不再扩展内置对象是个不错的主意。但是,它并没有解决手头的问题。即使 MooTools 发布补丁版本,所有使用它的现有网站也必须更新,这样兼容性问题才能消失。
6. 能不能只更新网站中使用的 MooTools 副本?
在理想情况下 MooTools 会发布一个补丁,每个使用 MooTools 的网站都会在第二天神奇地更新。问题解决了,对吧?!
不幸的是,这是不现实的。即使有人以某种方式识别了整套受影响的网站,也可以设法找到每一个网站的联系信息,成功地与所有网站所有者联系并说服他们全部执行更新(这可能意味着重构他们的网站完整的代码库),整个过程最多需要几年的时间。
请记住,这些网站很多都是旧的,可能无法维护。即使维护人员仍然在身边,也可能他们不是像您一样的高技能 Web 开发人员。由于网络兼容性问题,我们不能指望每个人都去改变他们已经运行了七八年的网站。
7. TC39 的工作流程是什么样的?
JavaScript 语言基于 ECMAScript 标准,TC39 是负责 JavaScript 语言更新发展的委员会
“Smoosh门”事件使得一些人误认为“TC39 想要把 flatten
重新命名为 smoosh
”,但这是一个没有很好沟通的笑话。重命名提案等重大决策不会被轻视,不会被单个人采纳,并且绝对不会在 GitHub 的评论上完成。
TC39 对于功能提案有着清晰得分级过程。ECMAScript 提案及其任何重大变更(包括方法更新)在 TC39 会议期间进行讨论,并且需要整个委员会批准后方可正式提交。在这种情况下 Array.prototype.flatten
提案已经经历了好几个阶段的讨论,一直到 Stage 3,表明该功能已准备好在 Web 浏览器中实现。实施过程中出现其他规范问题很常见。在这种情况下,最重要的反馈意见是在试图发布它之后才有的:该特性在当前状态下打破了 Web。即使浏览器发布新功能后 TC39 的流程并没有结束,就是因为这些难以预测的问题。
TC39 以协商一致的方式运作,这意味着委员会必须就任何新的变化达成一致。即使 smoosh
是一个严肃的建议,委员会成员似乎也可能会反对,而是赞成使用更常见的名称,例如 compact
或 chain
。
把 flatten
重命名为 smoosh
(即使它不是一个笑话)从未在 TC39 会议上讨论。因此,关于这个问题的官方 TC39 立场目前是未知的。在下次会议达成共识之前,没有任何一个人可以代表所有 TC39 发言。
TC39 会议通常由具有高度多样化背景的人士出席:一些人拥有多年的编程语言设计经验,另一些人使用浏览器或 JavaScript 引擎工作,越来越多的 JavaScript 开发人员社区参与者。
8. 接下来发生什么?
下一次 TC39 会议将于本周举行。议程中有一项讨论 flatten
及其网络兼容性问题。希望在会议结束后我们会更多地了解下一步。