相信每一個前端工程師都或多或少遇上過“亂碼”這位仁兄,無論你的基礎有多麼扎實,在生產的過程中都免不了偶爾和“亂碼”兄弟喝上幾杯茶吧。作為一個前端工程師,你是如何指定一個頁面的編碼的呢?你知道瀏覽器是怎麼識別編碼的嗎? 首先,一個很簡單的例子,用遇簡的HTML頁面來看看各瀏覽器下有什麼不同: 最簡HT
相信每一個前端工程師都或多或少遇上過“亂碼”這位仁兄,無論你的基礎有多麼扎實,在生產的過程中都免不了偶爾和“亂碼”兄弟喝上幾杯茶吧。作為一個前端工程師,你是如何指定一個頁面的編碼的呢?你知道瀏覽器是怎麼識別編碼的嗎?
首先,一個很簡單的例子,用遇簡的HTML頁面來看看各瀏覽器下有什麼不同:
<!DOCTYPE html>
最簡HTML,<head>
和<body>
都沒有內容,伺服器也不給出具體的編碼聲明,直接從本地打開,各個瀏覽器下查看頁面的編碼:
瀏覽器 | 顯示編碼 | 備註 |
---|---|---|
IE6 | UTF-8 | |
IE8 | UTF-8 | |
IE9 | GB2312 | 系統預設字元集 |
Firefox3.5 | GBK2312 | 系統預設字元集 |
Firefox4.0 | ISO-8859-1 | 西歐語言,英語預設編碼 |
Chrome | GBK | 系統預設字元集 |
Opera | 中文-自動檢測 | 應該也是GB2312 |
從表格中可以看出,對於沒有使用任何手段聲明編碼的頁面,各瀏覽器有著不同的解析。當然在最簡的頁面中,無論用什麼編碼(當然前提是ASCII的超集)都沒有影響,但足以表現出正確設置編碼的重要性。
編碼聲明
HTML4和HTML5分別採用了一個章節來闡述編碼聲明的方法,可以點擊這裡查看HTML4的相關章節或點擊這裡查看HTML5的相關章節。
首先,何為編碼?編碼即是通過一定的方式,指定瀏覽器(或稱用戶代理)以一種特殊的演算法來解析位元組流,以得到真正正確的內容。在HTML的標準中,編碼可以使用別名來表示。編碼的別名來自於IANA的定義,只有在該列表中出現的編碼才可以被瀏覽器識別。因此如果把UTF-8寫成UTF8,瀏覽器就有可能完全不予理睬。另外,編碼別名是大小寫不敏感的。
在HTML4中,提出有3種方法指定頁面的編碼,根據優先順序高低依次是:
- HTTP頭裡的Content-Type欄位後跟隨字元集。
- 使用
<meta http-equiv="Content-Type">
標簽來聲明。 - 對於部分外部資源,如
<script>
標簽載入的js文件,可以通過標簽上的charset屬性聲明。
這個自然沒有什麼疑問,需要註意的是,通過<meta http-equiv="Content-Type">
標簽來聲明頁面的話,當瀏覽器遇上該標簽時,如果發現自己使用的編碼與標簽聲明的不符,是會回到頭裡重新解析頁面的。這會導致頁面的一部分被重新解析,因此如果試圖使用標簽的方式聲明編碼的話,建議將標簽儘可能地寫在前面。一個最佳實踐是寫在<head>
標簽之後,任何其他標簽之前。關於這一點,Google PageSpeed也有相應的介紹。
時代演進
但是隨著時間的推移,開發者漸漸發現了一件事。就如同DOCTYPE的最簡聲明一樣,其實瀏覽器在讀取<meta>
標簽的編碼的時候,並不是嚴格地按照標準進行的。總而言之,由於在HTML的解析階段,基於在Tokenizer階段之前就必須確定好頁面的編碼,因此瀏覽器不可能像分析DOM樹一樣,在DOM樹構建的時候再分解<meta>
標簽的結構,取出其中的http-equiv
和content
屬性,再確定編碼。
現實中,瀏覽器做了一件非常簡單的事,來讀取<meta>
標簽定義的編碼:
- 確定這是一個
<meta>
標簽,這根據HTML解析的狀態機,由"<"字元加上"meta"字元串就能確定。 - 查找該字元串(此處還沒有標簽的概念,只是個字元串),找到一個子字元串"charset"。
- 再向後讀,忽略掉所有的空格字元,找到第一個有意義的字元c。
- 如果c不是"="這個字元,則回到第2步繼續找。
- 如果c是"="這個字元,繼續向下走。
- 再跳掉所有的空格字元和單引號、雙引號等,向後掃描,直到遇上單引號、雙引號、空格字元、結束標簽等不應該出現的字元為上,截取其中掃描得出的字元串s。
- 分析s,得到編碼別名。
從上面的演算法,不難發現,下麵幾種寫法,其實都能讓瀏覽器正確地識別出編碼:
<meta http-equiv="Cotnent-Type" content="text/html; charset=utf-8" />
<meta charset="utf-8" />
<meta charset=utf-8 />
- ……以及其他很多古怪的寫法。
於是,隨著歷史的推進,終於有一天,各瀏覽器廠商們坐在了一起,開始討論這個問題……最終他們驚奇地發現各自的實現非常相似(也許根本就是相互借鑒),所以他們決定將這種方式變成一個標準……最後,再經過漫長的討論,HTML5中廣為人愛的編碼聲明方式就誕生了。在HTML5中,稱其為“meta charset元素”,其最簡形式如下:
<meta charset=utf-8>
當然這是HTML的語法,如果遵從XHTML並覺得XHTML更加親切地話,寫成<meta charset="utf-8" />
也是沒問題的。
而前文所述的具體獲取編碼的演算法也被詳細地記錄在案,可以在這裡看到。
到了HTML5時代,標準再次對編碼的聲明方式做了修正和細化,總得來說有以下的區別:
- HTML5允許使用BOM來決定編碼,但僅支持UTF-16的BOM(即U+FEFF),且沒有說明BOM指定編碼的優先順序如何。
- HTML5添加了
meta charset
標簽。 - HTML5規定如果一個頁面沒有指定編碼,則使用ASCII作為其編碼,而HTML4則規定瀏覽器可以根據所處的環境自行選擇。
其他雜項
除了編碼的基本聲明方式外,標準中還有不少需要註意的細節:
- 如果使用
<meta>
標簽聲明編碼的話,該編碼只能是ASCII的超集編碼。可以簡單地認為ASCII超集就是支持ASCII的256個字元的編碼。 - HTML5非常推薦使用UTF-8編碼。
- 標準中提出不要使用UTF-32、JIS_C6226-1983、JIS_X0212-1990、HZ-GB-2312、JOHAB等字元集,並禁止使用CESU-8、UTF-7、BOCU-1和SCSU字元集。但事實上瀏覽器卻至少能識別UTF-7。
- 對於想要嚴格遵守XHTML的開發者,應當使用XML聲明來指定編碼,即
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
。但是這個在IE6下會影響到DOCTYPE,所以開發者也不得在這一點上給予妥協,乖乖地去用HTML的聲明方式。 - 關於現實中各編碼聲明方式的優先順序,以及一些其他需要註意的細節,這篇文章值得一讀。
最佳實踐
- 儘可能使用HTTP頭指定編碼。
- 儘可能使用UTF-8,或者至少全站所有資源使用統一編碼。
- 如果想使用UTF-16,就給文件加上BOM,以確定是Little Endian還是Big Endian的。
- 如果使用
<meta>
標簽指定編碼,可以不使用http-equiv的形式,但儘可能讓標簽出現在前面,至少保證在任何非ASCII字元之前。 - 鏈接外部的腳本,如果無法確定編碼相同的話,加上charset屬性。