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 中發布。