iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 16
2
Modern Web

前端三十 - 成為更好的前端工程師系列 第 16

16. [FE] 為何會有瀏覽器差異?怎麼處理?

https://images.unsplash.com/photo-1518707598572-8cf7dabd8f66?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80

在本系列文的前兩週,我們深入認識了 HTML、CSS、JavaScript 這前端三兄弟中的許多細節,過程中筆者也提到了數次瀏覽器差異可能會造成的影響;今天再提供一個 瀏覽器差異的範例,明明在同一個網頁,同樣的 CSS 設定,在不同瀏覽器中卻有截然不同的顯示效果:

https://ithelp.ithome.com.tw/upload/images/20191002/20111380Z0zJV4XaWi.png

上圖為 OS X 中的 Safari 及 Chrome 的顯示結果

是不是很神奇?但為什麼一樣是瀏覽器,卻會有這麼大的差異呢?

差異的來源

這就要從瀏覽器大戰開始說起了。

由於瀏覽器是使用者探索網路的入口,從 1990 年代開始,各家廠商都依據當時的 HTML 規範,實作出了瀏覽器;脫穎而出的兩強分別為市場的先驅者網景 Netscape Navigator,以及壟斷個人電腦作業系統的微軟 Internet Explorer;為了搶佔更高的市佔率,兩家公司都開始實作標準外的功能,甚至忽視當時的標準、設計個別專屬的格式,迫使當時的開發者必須在網頁上標註「用 ___ 瀏覽器方可得到最佳瀏覽效果」,如此的行為又更強化了標準分歧。最終微軟挾帶豐富的功能及龐大財力,擊敗網景,結束了第一次瀏覽器大戰。

後續的瀏覽器大戰內容我就不贅述了,有興趣的讀者可以自行 Google,相信能輕易找到許多當時的紀錄 & 鄉野奇談;但從第一次瀏覽器大站的故事就能反映出,瀏覽器差異的根源,就在於「規範與實作之間的差距」。

規範與實作

瀏覽器的實作必須遵從幾個單位的規範:

  • ECMA:制定標準的 ECMAScript,這也就是 ES6 名稱的由來:ECMAScript 6
  • W3C:全球資訊網協會,負責制定網頁內容的標準,包含 HTML、CSS、DOM、XML 等等

ECMAScript 是標準,JavaScript 是標準的一種實現,雖然口語上時常混用,但還是提醒讀者要稍微留心,不要搞混囉!

這些標準制定組織,會在與社群反覆討論的過程中,逐漸更新標準;例如你可以到 TC39 的 Github 頁面 ,觀看最新被提出來的 ECMAScript 功能草案。在新標準草案被提出,並能得到大部分討論者的正面回饋時,瀏覽器開發者便會著手實作這些功能;通常新功能由於還在實驗性質,可能會需要使用者到設定頁面自行開啟,或是在程式參數、樣式屬性名稱中加上特定的前綴字。

當然,現在的瀏覽器不太會像當年大戰時各自搞特規,讓我們開發者叫苦連天;不過日子只能往前走,一個方向順時鐘,由於屬性可能被棄用、預設設定可能會改動,即便瀏覽器符合現今的標準,也不等於它能夠符合未來的標準,而這也就成了我們需要著手處理的地方了。

解決方案

說了那麼多差異,那麼實務上該如何解決呢?首先要認清的是,支援所有的瀏覽器是不太現實的,畢竟版本不斷更迭,一直不斷向下相容的話,開發上會完全不能使用新功能,可能執行效能、開發效率都會大打折扣;如果在可以放棄過舊瀏覽器的前提下,盡可能的擴張支援的範圍,那麼就有可以優化的地方了。

為了消弭瀏覽器差異,在針對不同的情況中,有不同的處理方法:

樣式差異

CSS 的部份,可以加上實驗階段的樣式名稱。例如很好用的 flex 排版:

display: -webkit-box; 
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex; 
display: flex;

如上例,在尚未成為標準時,各家瀏覽器的名稱除了加上前綴外,還有可能屬性名字完全不一樣;更何況一個網站絕對不會只有少少幾條樣式需要加上屬性,這種情況靠人工處理是不太現實的;這時候就要仰賴其他套件做 CSS 後處理啦~

例如很實用的 Autoprefixer,只要透過 Browserslist 設定欲支援的瀏覽器版本;例如 'cover 99.5%' 指定要支援最流行的 99.5% 瀏覽器、'> .5% or last 2 versions' 指定要支援市佔率超過 5% 或最新兩個版本的瀏覽器、'not ie < 11' 指定不支援 ie < 11 的版本等等,藉由簡單的設定,就能自動幫開發者解決樣式的瀏覽器差異問題囉。

程式差異

瀏覽器開發者撰寫 Web API 時,也會有如同 CSS 加前綴字的作法,變成有前綴字的屬性。例如 去年我鐵人賽在玩的 Web Audio API

window.AudioContext = window.AudioContext || window.webkitAudioContext

AudioContext 對於一部分舊版的瀏覽器,可以透過這樣的設定,讓環境變數暫時設定成實驗性變數,也許功能就得以正常運作了。

對於完全沒有實驗性質變數的情況,也有別的處理方法;例如 昨天文中 提到的 Polyfill,就是一種解決方案。Polyfill 的概念很簡單,就是:原生沒有這個功能,那就從現有的功能重新實作類似的功能。開發者可以透過 Polyfill.io 之類的網站,設定要支援的功能版本,並將補丁檔引入到專案中。

例如我們看一個較簡單的,isNaN 函式的補丁:

// Number.isNaN
/* global CreateMethodProperty, Type */
(function () {
	var that = this;
	// 20.1.2.4. Number.isNaN ( number )
	CreateMethodProperty(Number, 'isNaN', function isNaN(number) {
		// 1. If Type(number) is not Number, return false.
		if (Type(number) !== 'number') {
			return false;
		}
		// 2. If number is NaN, return true.
		if (that.isNaN(number)) {
			return true;
		}
		// 3. Otherwise, return false.
		return false;
	});
}());

有兩個內部函式,不過功能都很單純, CreateMethodProperty 就只是把補丁函式及函式名稱綁到指定的原生物件上,而 Type 則是將 typeof 的結果做一些處理,例如判斷是不是 Symbol 之類的。其他內容的程式、註解都非常的詳細易懂,相信應該沒有麼問題;有興趣進一步學習的讀者,也可以再自行觀摩例如 Polyfill.io 透過 Array 實作的 Map、Set,都蠻有意思。

相比於 Polyfill 這種補足瀏覽器的解決方案,相反的有另一種作法則是將程式碼經過程式處理,讓它能向下符合目標版本的環境。最有名的套件莫過於 Babel 了,例如官網範例:

// 處理前
[1, 2, 3].map(c => c ** 2)

// 處理後
[1, 2, 3].map(function (c) {
  return Math.pow(c, 2)
})

這邊用到了 ES6 的箭頭函式,及 ES7 的指數運算子,經過 Babel 處理後都轉成了 ES5 或更早版本就支援的原生語法。因為有 Babel 這種強大的工具,前端開發者便能肆無忌憚的使用最新的語法,並且不用花太多時間處理使用者瀏覽器相容的問題,大幅度的提升開發效率及開發者體驗。

結語

當然,有許多瀏覽器獨有的特殊眉角,文中礙於篇幅沒有特別說明,可能也只有踩過雷的人才知道;但身為開發者的我們面對這種瀏覽器差異,也真的沒有什麼辦法,就只能靠經驗累積,多踩幾次雷就知道了 XD

今天從第一次瀏覽器大戰出發,藉由歷史案例,一窺當標準與實作嚴重脫節時會發生什麼事情;並分享了一部分解決瀏覽器差異的方法。雖然這些方法不見得能處理所有的情況,但應付一般的情況絕對是綽綽有餘啦~

本系列前端篇的第一篇就到這邊啦,明天記得回來繼續旅程喔~

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
15. [JS] 什麼是原型鏈?
下一篇
17. [FE] 為什麼現在的前端都在用「框架」?
系列文
前端三十 - 成為更好的前端工程師31

尚未有邦友留言

立即登入留言