今天遇到了一個奇怪的問題,兩次ajax發(fā)送的同一個變量值,后端接收到的編碼不一樣……,一時間,我竟然發(fā)現(xiàn)自己對于編碼的問題不能說的很清楚。
lisp主張代碼即數(shù)據(jù),其實我們寫的代碼也是數(shù)據(jù)(信息),數(shù)據(jù)的存儲和傳播都要就要涉及到編碼的問題。就像我們向?qū)Ψ絺鬟f信息之前,先要問對方:can you spreak in english。
小貼士:嗨,你知道嗎!windows的換行符是 \r\l,linux的是 \l,mac的是 \r,這是有意還是故意的呢……
本文會試圖說清web開發(fā)過程中,如下方面的編碼問題:
如果想把編碼問題說清楚,那恐怕你看到這里就會把頁面關掉了,所以這里僅簡要介紹下國內(nèi)web開發(fā)中常用的一些編碼。如果你了解或不感興趣,可以直接跳過本部分。
進入數(shù)字時代,整個世界都要數(shù)字化,最先數(shù)字化的就是文字,老外的自我中心論,以為世上僅有abcd…… 26個字母,便發(fā)明了ASCII(美國標準信息交換碼)。
ASCII 使用一個字節(jié)的低7位的不同組合來表示字母數(shù)字和一些其他字符,2^7 = 128
7位二進制共可以代表128個字母。
后來為了表示更多的字符便將ASCII擴展為8位,稱為EASCII,并等同于國際標準ISO/IEC 646。
而我們?yōu)榱俗対h語也能數(shù)字化,變發(fā)明了gb2312,gb2312使用兩個字節(jié)16位表示漢字,為了兼容ASCII將每個字節(jié)的高8位置為1,共有14位可用,2^14 = 16384,但GB 2312標準共收錄6763個漢字,后來發(fā)現(xiàn)不太夠用,又擴展了gbk,gb18030。
終于老外發(fā)明了 Unicode,一切都解決了,Unicode有兩個字節(jié)的16位的編碼空間,Unicode是一個歸法,比較常用的有UTF8和UTF16兩種編碼方式。
UTF8在web領域比較常用,是一種變長的編碼方式,UTF16是一種定長方式。
最后來看一下,‘回’字的不同編碼,要記住哦,后面會多次用到。
文件編碼也可以說是存儲的編碼。
我們寫的代碼,最終會以二進制的方式,持久化到計算機的存儲設備中。
不同編碼的文件,會以不同的規(guī)則存儲到磁盤中,不同的編碼要有自己獨特的規(guī)則,這樣才能在讀取的時候不發(fā)生沖突,也就是要能識別出來自己,計算機在打開文件的時候都是靠猜測來解碼的。
如果存儲的編碼(算法)和打開的編碼(算法)不一致,就會出現(xiàn)亂碼的情況。
文件在網(wǎng)絡上傳入要聲明自己的編碼,我們一定要清楚源文件的編碼,然后才能進行后序工作。
對于前端而言會涉及html,css和js源文件的編碼,而國內(nèi)常用的編碼如下:
推薦大家用utf-8,不容易出問題,但由于歷史原因,國內(nèi)有很多網(wǎng)站的編碼都是gbk,下面是BAT的網(wǎng)站情況:
百度歷史比較悠久的產(chǎn)品線的編碼也是gbk,比如百度知道。
網(wǎng)頁中HTML的編碼由哪些部分決定呢?真正運行在服務器上的HTML編碼由下面幾部分決定
上面給出的三個因素的權(quán)重是由高到低,也就是說HTTP頭會覆蓋meta的信息。
如果你還不知道什么是HTTP頭那么請自行百度,HTTP頭中的編碼信息如下圖中紅色方塊圈起來的部分,代表當前頁面的編碼是utf8。用戶代理收到這個信息后,就會用utf8編碼來將收到的字節(jié)流解碼。
有些服務器會設置文件的默認編碼信息,有些則不會,后端語言都可以自行設置頁面的html編碼,在php中設置http編碼代碼如下:
header("Content-Type:text/html; charset=utf-8");
如果沒有設置http頭信息,或者在文件系統(tǒng)直接打開頁面,html的meta標簽就派上了用場,meta中可以設置http頭信息,下面的代碼和上面php的功能相同:
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
在html5中將上面的代碼簡化為如下形式:
<meta charset="utf-8">
如果既沒有設置http頭,也沒有meta標簽,那么用戶代碼會使用系統(tǒng)的默認設置,windows下的中文環(huán)境的默認編碼一般是 gb2312,所以用戶代理就會用gb2312來解碼頁面,如果頁面的編碼也剛好是gb2312那么萬事ok,否則就會出現(xiàn)亂碼。
用戶代理一般可以設置默認的編碼是什么。比如chrome打開設置下的內(nèi)容網(wǎng)絡,會看到如下的設置界面。
注:如果最終顯示的編碼和頁面本身的編碼不同就會出現(xiàn)亂碼。
說完了HTML的編碼再來說說CSS的編碼,如果HTML文件和CSS文件的編碼不一致,那么就需要單獨聲明才可以。
在html4中l(wèi)ink有一個屬性——charset可以用來指定引入css文件的編碼,但這個屬性在html5中已經(jīng)廢棄了,雖然廢棄了但在瀏覽器中還是可以使用的,下面的代碼顯示指定css文件的編碼為gbk。
<link rel="stylesheet" href="gbk-1.css" charset="gbk">
然而html5廢棄了這個屬性那么該怎么辦呢,其實廢棄這個屬性,是因為把這個功能代理給了css文件,css中有一個@charset指定,可以指定頁面的編碼,將下面的代碼放在css文件的頂部,會顯示聲明頁面的編碼為utf8。
@charset utf-8
那么問題來了,如果即設置了charset屬性,又設置了@charset指定,結(jié)果是@charset指定會覆蓋link的charset屬性,charset屬性已經(jīng)廢棄了,建議用css的@charset指令。
注:如果我們顯示指定的編碼和css文件的編碼不一致也會導致亂碼問題。
說清了CSS的編碼,我們再來說JavaScript的編碼問題,首先還是如果HTML文件的編碼和JavaScript文件的編碼不一致的話就會產(chǎn)生亂碼,這時就需要我們顯示聲明一下js文件的編碼才可以。
html中的script標簽有一個charset屬性,用來指定引入外部jss文件的編碼(字符集)。下面的代碼顯示聲明引入的js的編碼是utf8。
<script src="***.js" charset="utf-8"></script>
注:如果我們顯示指定的編碼和js文件的編碼不一致也會導致亂碼問題。
其實在JavaScript中僅支持utf16編碼,其實也不是utf16,而是utf16的子集——ucs-2,這導致在js中無法表示BMP之外的文字(更多信息請看這里)。
無論js源文件的編碼是什么,下面的源代碼的輸出結(jié)果是一樣的——前提是解碼js的過程正確。
'回'.charCodeAt(0)#輸出 22238
22238的十六進制表示是56 DE,而這正是‘回’字的utf16編碼。
說ajax之前,先來說說表單提交吧,下面分別是utf8頁面和gbk頁面的表單提交請求,其中參數(shù)name是’回’字??梢钥闯霰韱握埱蟮木幋a是由頁面決定的。
http://localhost/github/webtest/charset/form.php?name=%E5%9B%9E
http://localhost/github/webtest/charset/form.php?name=%BB%D8
ajax發(fā)送的代碼也是由頁面決定的,但我們在發(fā)送參數(shù)前都會encodeURIComponent一下,encodeURIComponent不管頁面編碼是什么,都會返回utf8編碼。
讓我們構(gòu)造一個例子,頁面A的編碼為gbk,有如下的代碼:
xhr.open("GET","form.php?name=" + encodeURIComponent('回') + 'namegbk=' + '回',true);
我們看到發(fā)送的ajax請求如下所示:
http://localhost/github/webtest/charset/form.php?name=%E5%9B%9Enamegbk=%BB%D8
這個例子巧妙的驗證了上面的結(jié)論。
再來說說json,json只支持utf8編碼,所以不要返回gbk編碼的json。
在信息交換的任何一個環(huán)節(jié),如果編碼信息和解碼信息不一致都會產(chǎn)生亂碼問題。
本文中的所有例子都可以從webtest下載。
關于編碼還有很多東西可以需要學習,我強烈建議你可以閱讀下參考資料里面的一些文章,同時我還建議您閱讀”code“這本書。
更多建議: