開發跨瀏覽器策略#
開發能在多種瀏覽器中運作的 JavaScript 程式碼,是一項需要謹慎平衡開發方法論和可用資源的工作。本章探討如何選擇要支援的瀏覽器、跨瀏覽器開發的主要關注點,以及有效的實作策略。
14.1 跨瀏覽器考量#
選擇支援哪些瀏覽器時,意味著以下承諾:
- 主動對該瀏覽器執行測試套件
- 修復與該瀏覽器相關的 bug 和 regression
- 確保程式碼在該瀏覽器上有合理的效能表現
Graded Browser Support#
借鑒 Yahoo! 的做法,建立 browser-support matrix(瀏覽器支援矩陣)——以平台為一軸、瀏覽器為另一軸,為每個組合分配等級(A 到 F),反映支援的重要性和優先順序。
決定支援範圍時需考量的因素:
- 目標受眾的期望和需求
- 瀏覽器的市場佔有率
- 支援該瀏覽器所需的工作量
品質絕不應為了覆蓋率而犧牲。 不要嘗試超出能力範圍的支援——寧可在較少的瀏覽器上做好,也不要在很多瀏覽器上做得馬馬虎虎。
14.2 五大開發關注點#
開發可重用 JavaScript 程式碼時,面臨五個主要挑戰:
- Browser bugs(瀏覽器 bug)
- Browser bug fixes(瀏覽器 bug 修復)
- External code(外部程式碼)
- Browser regressions(瀏覽器回歸)
- Missing features(缺少的功能)
瀏覽器 Bug 與差異#
- 程式碼的功能必須在所有支援的瀏覽器中可驗證地正確
- 需要完善的測試套件,涵蓋常見和邊界使用場景
- 實作修復時要注意,修復方式不應在瀏覽器修正 bug 後反而造成問題
範例:scrollTop bug
IE 11 和 Firefox 遵循規範在 html 根元素上使用 scrollTop/scrollLeft,而 Safari、Chrome、Opera 需要在 body 元素上操作。開發者若透過 user agent 偵測來處理這個差異,反而可能在瀏覽器修正後造成問題。
判斷某功能是否為 bug 時,務必參照規範驗證。未經規範的 API 可能隨時變更,要特別注意。
瀏覽器 Bug 修復#
- 不要假設 bug 會永遠存在——大多數 bug 最終會被修復
- 依賴 bug 存在的修補程式(workaround),在 bug 被修復後可能反而導致破壞
- 應使用 14.3 節的技巧確保修補方案盡可能不受未來變更影響
外部程式碼與標記#
可重用程式碼必須能與周圍的程式碼共存,需注意:
封裝程式碼:
- 保持極小的全域足跡(global footprint)
- 像 jQuery 一樣,只暴露一個全域變數作為命名空間
- 避免修改既有變數、函式原型或 DOM 元素
var ninja = {}; // 單一命名空間
ninja.hitsuke = function () {
/* ... */
};應對品質不佳的外部程式碼:
- 外部程式碼可能修改函式原型、物件屬性和 DOM 元素方法
- 要做好防禦性程式設計,假設最壞的情況
DOM Clobbering 問題:
瀏覽器會將 <form> 內的 input 元素以其 id 或 name 值作為 form 元素的屬性。若 id/name 值與既有屬性衝突(如 action、submit),原始屬性會被覆蓋:
<form id="form" action="/conceal">
<input type="text" id="action" />
<input type="submit" id="submit" />
</form>document.getElementById("form").action; // 回傳 input 元素而非 "/conceal"
document.getElementById("form").submit(); // TypeError!
避免使用與標準 DOM 屬性名稱相同的
id和name值,特別要避免使用submit。
樣式表與腳本的載入順序:
確保外部樣式表在腳本之前載入,否則腳本可能存取到尚未定義的樣式資訊。
Regressions(回歸)#
Regressions 是瀏覽器引入的非向後相容 API 變更或 bug,導致原本正常的程式碼失效。
預見變更:
function bindEvent(element, type, handle) {
if (element.addEventListener) {
element.addEventListener(type, handle, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handle);
}
}透過 feature detection 優先使用標準 API,若不支援再回退到專有 API。
持續監控瀏覽器更新: 定期在新版瀏覽器上執行測試,追蹤各瀏覽器的 nightly builds 和開發部落格。
14.3 實作策略#
安全的跨瀏覽器修復#
最簡單、最安全的修復具備兩個特性:
- 不會在其他瀏覽器產生負面效果或副作用
- 不使用任何形式的瀏覽器或功能偵測
範例 1: jQuery 忽略所有瀏覽器中 height 和 width 的負值設定(因為某些版本的 IE 會因此拋出例外)。
範例 2: jQuery 禁止修改已在 DOM 中的 <input> 元素的 type 屬性(因 IE 不允許此操作),統一在所有瀏覽器中拋出資訊性例外。
jQuery 團隊經過權衡,認為提供一致性的 API 比保留某些只在特定瀏覽器才有的功能更重要。開發自己的可重用程式碼時,也可能需要做出類似的取捨。
Feature Detection 與 Polyfills#
Feature detection 透過檢查特定物件或屬性是否存在,來判斷功能是否可用:
if (
typeof document !== "undefined" &&
document.addEventListener &&
document.querySelector &&
document.querySelectorAll
) {
// 有足夠的 API 可以建構應用
} else {
// 提供 fallback
}Polyfill 是瀏覽器 fallback——若瀏覽器不支援某功能,提供自己的實作:
if (!Array.prototype.find) {
Array.prototype.find = function (predicate) {
if (this === null) {
throw new TypeError("find called on null or undefined");
}
if (typeof predicate !== "function") {
throw new TypeError("predicate must be a function");
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}Fallback 的選項包括:
- 進一步偵測功能,提供部分 JavaScript 功能的精簡體驗
- 不執行 JavaScript,退回到未腳本化的 HTML
- 導向網站的簡化版本(如 Google 對 Gmail 的做法)
無法測試的瀏覽器問題#
有些問題在技術上無法或難以測試:
- Event handler bindings:無法程式化地判斷元素是否已綁定事件 handler
- Event firing:無法確定瀏覽器是否會觸發特定事件
- CSS property effects:無法程式化地驗證修改 CSS 屬性(如
color、opacity)是否真的影響了視覺呈現 - Browser crashes:導致瀏覽器當機的程式碼無法被測試,因為測試本身就會造成當機
- API performance:某些 API 在不同瀏覽器中速度差異很大,但效能分析成本高,不適合在每次頁面載入時執行
- Incongruous APIs:有時必須在所有瀏覽器中禁用某功能來繞過特定瀏覽器的 bug
14.4 減少假設#
跨瀏覽器程式碼是一場與假設的對抗。透過巧妙的偵測和設計,可以減少程式碼中的假設數量。
User-agent 偵測的問題:
- 分析
navigator.userAgent來推斷瀏覽器行為(browser detection)是常見但危險的做法 - 假設某個 bug 或功能永遠與特定瀏覽器綁定是災難的根源
- Feature detection 優於 browser detection
以 bindEvent 為例的三個隱含假設:
- 我們檢查的屬性是可呼叫的函式
- 它們是正確的函式,執行我們期望的動作
- 這兩種方法是綁定事件的唯一方式
消除所有假設是不可能的。開發者需要在減少假設和程式碼複雜度之間找到平衡。即使是假設最少的程式碼,仍然可能受到瀏覽器引入的 regression 影響。
14.5 本章重點#
- 瀏覽器雖然持續改善,但仍非無 bug,且通常不會一致地支援 web 標準
- 選擇支援哪些瀏覽器和平台是重要的決策,品質不應為覆蓋率犧牲
- 跨瀏覽器開發的最大挑戰:bug 修復、regressions、瀏覽器 bug、缺少的功能、外部程式碼
- 可重用的跨瀏覽器開發需要平衡多個因素:
- Code size — 保持檔案大小精簡
- Performance overhead — 維持可接受的效能水準
- API quality — 確保 API 跨瀏覽器行為一致
- 沒有確定正確平衡的萬能公式,開發者必須根據自身情況判斷
- 透過 feature detection 等技巧,可以在不做過度犧牲的情況下防禦可重用程式碼的各種攻擊面