SmooshGate 常見問題

Mathias Bynens
Mathias Bynens

發生了什麼「黏糊糊」的情況?

針對名為 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 屬性取得額外的迴圈疊代。

這裡更大的問題是修改內建物件。目前普遍認為擴充原生原型是一種不良做法,因為這類做法無法與其他程式庫和第三方程式碼完美結合。請勿修改您不具擁有權的物件!

為什麼不保留現有名稱,讓網路中斷?

在 CSS 普及之前,也就是「HTML5」出現之前很久之前,Space Jam 網站就已上線。如今,這個網站仍以 22 年前的方式運作。

為什麼會發生這種情況?這些年來,是否有人負責維護該網站,並在瀏覽器供應商推出新功能時更新網站?

事實上,「不要破壞網際網路」是 HTML、CSS、JavaScript 和其他在網際網路上廣泛使用的標準的首要設計原則。如果推出新的瀏覽器功能會導致現有網站停止運作,對所有人來說都是壞事:

  • 受影響網站的訪客突然遭遇使用者體驗中斷的情況。
  • 網站擁有者原本擁有正常運作的網站,但在未變更任何內容的情況下,網站變得無法運作;
  • 瀏覽器供應商推出新功能後,因使用者發現「這項功能在 X 瀏覽器中運作」而轉換瀏覽器,因此失去市場佔有率。
  • 一旦發現相容性問題,其他瀏覽器供應商就會拒絕發布。功能規格不符合實際情況 (「只是虛構作品」),這對標準化程序不利。

回顧過去,MooTools 確實做錯了,但讓網站無法運作並非懲罰他們,而是懲罰使用者。這些使用者不知道 moo 工具是什麼。或者,我們可以找出其他解決方案,讓使用者繼續使用網頁。選擇起來也相當簡單。

這是否表示無法從 Web 平台移除不良的 API?

視情況而定。在極少數情況下,可以從網路上移除惡意功能。即使只是想瞭解是否「可以」移除某項功能,也是一項非常棘手的工作,因為您必須進行廣泛的追蹤記錄,才能量化有多少網頁會因移除功能而改變行為。但如果功能不夠安全、對使用者有害,或很少使用,則可以這麼做。

<applet><keygen>showModalDialog() 都是從網路平台成功移除的壞 API 範例。

為什麼不直接修正 MooTools?

建議您修補 MooTools,讓它不再擴充內建物件。不過,這並無法解決目前的問題。即使 MooTools 發布修補版本,所有使用該工具的現有網站都必須更新,才能解決相容性問題。

使用者不能直接更新自己的 MooTools 副本嗎?

在理想情況下,MooTools 會發布修補程式,而每個使用 MooTools 的網站都會在隔天神奇地更新。問題解決了,對吧?

很抱歉,這並非現實可行的做法。即使有人能夠找出所有受影響的網站,並設法取得每個網站的聯絡資訊,成功與所有網站擁有者聯絡,並說服他們進行更新 (這可能意味著重構整個程式碼庫),整個程序也至少需要好幾年。

請注意,這些網站大多已過時,且可能未經維護。即使維護者仍在線上,也可能不是像您一樣具備高超技能的網頁程式開發人員。我們無法要求所有人因為網站相容性問題而變更 8 年前的網站。

TC39 程序的運作方式為何?

TC39 是負責透過 ECMAScript 標準發展 JavaScript 語言的委員會。

#SmooshGate 讓部分人認為「TC39 想將 flatten 重新命名為 smoosh」,但這只是內部笑話,並未向外部妥善溝通。像是變更提案名稱這類重大決策,我們不會輕易做出決定,也不會由單一人員做出決定,更不會在一天內就根據單一 GitHub 留言做出決定。

TC39 會針對功能提案採用明確的測試環境程序。在 TC39 會議中,我們會討論 ECMAScript 提案和任何重大變更 (包括方法重新命名),並在獲得全委員會的核准後才正式發布。以 Array.prototype.flatten 為例,該提案已通過多個階段的協議,並一路前進到第 3 階段,表示這項功能已可在網頁瀏覽器中實作。在實作過程中,常會出現其他規格問題。在本案例中,最重要的意見回饋是在嘗試發布功能後才出現:在目前狀態下,這項功能會導致網頁發生錯誤。這類難以預測的問題,是 TC39 程序不只在瀏覽器推出功能後結束的原因之一。

TC39 是依據共識運作,也就是說,委員會必須同意任何新的變更。即使 smoosh 是認真的建議,委員會成員還是可能會反對,並支持使用更常見的名稱,例如 compactchain

flatten 改為 smoosh 的名稱變更 (即使不是開玩笑),從未在 TC39 會議中討論過。因此,目前尚不清楚 TC39 對這個主題的官方立場為何。在下次會議達成共識之前,任何人都無法代表 TC39 發言。

參加 TC39 會議的人士通常背景各異:有些人擁有多年的程式設計經驗,有些人則是瀏覽器或 JavaScript 引擎的開發人員,此外,越來越多 JavaScript 開發人員社群成員也參與會議。

SmooshGate 最終是如何解決的?

2018 年 5 月的 TC39 會議中,#SmooshGate 問題正式解決,我們將 flatten 重新命名為 flat

Array.prototype.flatArray.prototype.flatMap 已在 V8 6.9 版和 Chrome 69 中發布。