SmooshGate 常见问题解答

Mathias Bynens
Mathias Bynens

Smoosh 发生了什么?!

一项名为 Array.prototype.flatten 的 JavaScript 语言功能的提案结果证明与网页不兼容。在 Firefox Nightly 中分发该功能会导致至少一个热门网站无法正常运行。鉴于有问题的代码是广为传播的 MooTools 库的一部分,因此可能影响了更多网站。(虽然 MooTools 在 2018 年不常用于新网站,但它曾经非常受欢迎,仍然存在于许多生产网站上。)

提案作者开玩笑地建议了将 flatten 重命名为 smoosh,以避免兼容性问题。这个笑话对所有人来说并不清楚,一些人开始错误地认为新名字已经确定,而事情迅速升级。

Array.prototype.flatten的用途

Array.prototype.flat(最初提议为 Array.prototype.flatten),以递归方式将数组扁平化,直到指定的 depth(默认值为 1)为止。

// Flatten one level:
const array = [1, [2, [3]]];
array.flat();
// → [1, 2, [3]]

// Flatten recursively until the array contains no more nested arrays:
array.flat(Infinity);
// → [1, 2, 3]

同一方案包含 Array.prototype.flatMap,它与 Array.prototype.map 类似,不同之处在于它将结果展平为新的数组。

[2, 3, 4].flatMap((x) => [x, x * 2]);
// → [2, 4, 3, 6, 4, 8]

MooTools 是执行什么操作导致这一问题的?

MooTools 定义了自己的非标准 Array.prototype.flatten 版本:

Array.prototype.flatten = /* non-standard implementation */;

MooTools 的 flatten 实现与建议的标准不同。不过,这不是问题!当浏览器以原生方式提供 Array.prototype.flatten 时,MooTools 会替换原生实现。这样可确保无论原生 flatten 是否可用,依赖 MooTools 行为的代码都能按预期运行。到目前为止,一切进展顺利!

遗憾的是,之后发生了其他事情。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 属性进行额外的循环迭代。

此处更大的底层问题是修改内置对象。如今,扩展原生原型通常被认为是一种不好的做法,因为这样做与其他库和第三方代码无法很好地组合起来。请勿修改并非您所有的对象!

我们为什么不保留现有名称并破坏网络?

1996 年,在 CSS 面世之前,早在“HTML5”问世之前,Space Jam 网站便上线了。如今,该网站的运作方式仍然与 22 年前相同。

你是如何做到的?是否有人维护了这么多年的网站,每次浏览器供应商推出新功能时都会更新该网站?

事实证明,“不破坏网络”是 HTML、CSS、JavaScript 以及网络上广泛使用的任何其他标准的首要设计原则。如果推出新的浏览器功能会导致现有网站停止运行,这对所有人都很不利:

  • 受影响网站的访问者突然遇到糟糕的用户体验;
  • 网站所有者从拥有完全正常运转的网站发展成为了一个无法正常运作的网站,而未做出任何改动;
  • 推出新功能的浏览器供应商会失去市场份额,这是因为用户在注意到“可在浏览器 X 中正常运行”后切换浏览器;
  • 明了此兼容性问题后,其他浏览器供应商就会拒绝发布。功能规范与现实(“没有什么,只有虚构的作品”)不符,这对标准化过程而言是不利的。

当然,回头看来 MooTools 做错了事,但是破坏网络并不会使他们受到惩罚,而是会给用户造成不利影响。这些用户不知道什么是 Moo 工具。或者,我们可以找到另一种解决方案,用户可以继续使用网页。做出选择非常简单。

这是否意味着一定不能从 Web 平台中移除不良 API?

这要视具体情况而定。在极少数情况下,我们会从 Web 中移除不良功能。即使只是确定是否有可能移除某项功能,也是一项非常棘手的工作,需要大量的遥测数据来量化有多少网页会改变其行为。但是,当功能足够不安全、对用户有害或很少使用时,可以做到这一点。

<applet><keygen>showModalDialog() 都是已成功从 Web 平台中移除的不良 API 的示例。

为什么不直接修复 MooTools?

建议为 MooTools 打补丁,使其不再扩展内置对象。但是,这种方法并不能解决眼前的问题。即使 MooTools 发布修补版本,使用它的所有现有网站也必须进行更新,以消除兼容性问题。

用户不能直接更新 MooTools 副本吗?

理想情况下,MooTools 会发布一个补丁,每个使用 MooTools 的网站都会在次日神奇地进行更新。问题解决了,对吧?!

很遗憾,这是不现实的。即使有人通过某种方式确定了所有受影响的网站、设法找到每个网站的联系信息、成功联系所有网站所有者并说服他们执行更新(这可能意味着重构其整个代码库),整个过程最多可能需要数年时间。

请注意,其中许多网站都是旧网站,可能没有维护。 即使维护人员还在现场,他们也可能不是像您这样技能高超的 Web 开发者。由于网络兼容性问题,我们不能指望每个人都去更改他们已有 8 年时间的网站。

TC39 流程是如何运作的?

TC39 负责根据 ECMAScript 标准改进 JavaScript 语言。

#SmooshGate 让一些人相信“TC39 希望将 flatten 重命名为 smoosh”,但这只是在外部沟通不畅的玩笑。重命名提案等重大决策并非草率之举,也不是单个人做出的,绝对不会根据单个 GitHub 评论在一夜之间做出。

TC39 针对功能提案设有明确的预演流程。ECMAScript 提案及其所有重大变更(包括方法重命名)将在 TC39 会议期间讨论,且需要获得整个委员会的批准才能正式生效。就 Array.prototype.flatten 而言,提案已经经历了几个阶段的协议,一直到第 3 阶段,这表明该功能已准备好在网络浏览器中实现。在实现过程中,经常会出现其他规范问题。在本示例中,最重要的反馈是在尝试发送之后产生的:在当前状态下,该功能会破坏网络。这类难以预测的问题是 TC39 进程不仅会在浏览器推出功能后就结束的原因之一。

TC39 在协商的基础上运作,这意味着委员会必须就任何新的变更达成一致。即使 smoosh 是一个严肃的建议,委员会成员似乎也可能会反对意见,取而代之的是更常见的名称,如 compactchain

flatten 更名为 smoosh(即使这并不是玩笑)从未在 TC39 会议上讨论过。因此,TC39 在这一主题方面的官方立场目前尚不清楚。在下次会议达成共识之前,任何个人都不能代表 TC39 发言。

TC39 会议的参与者一般具有非常多样化的背景:有些人拥有多年的编程语言设计经验,有些人使用浏览器或 JavaScript 引擎,并且越来越多的同乘者代表了 JavaScript 开发者社区。

SmooshGate 最终是如何解决的?

2018 年 5 月 TC39 会议期间,#SmooshGate 通过将 flatten 重命名为 flat 正式解决。

V8 v6.9 和 Chrome 69 中提供了 Array.prototype.flatArray.prototype.flatMap