現在有個test.html文件,這個文件的編碼是UTF-8,其中“你好”的UTF-8編碼是:E4 BD A0 E5 A5 BD,文件代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> console.log(encodeURIComponent("你好")); //%E4%BD%A0%E5%A5%BD </script> </head> <body> <p>hello</p> </body> </html>
可以看見,enencodeURIComponent就是將“你好”以UTF-8編碼輸出(這也正是encodeURIComponent方法的定義:將非URI字符都以UTF-8編碼的格式輸出為字符串),現在來看一個怪異的情況,還是上面的test.html(所以文件的編碼依舊是UTF-8),只不過這次手動把charset改為了GBK編碼,用來誤導encodeURIComponent方法,代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="GBK"> <script> console.log(encodeURIComponent("你好")); //%E6%B5%A3%E7%8A%B2%E3%82%BD </script> </head> <body> <p>hello</p> </body> </html>
什么,輸出的是什么鬼!不着急,我們慢慢分析:
encodeURIComponent認為需要encode的字符的編碼是charset指定的編碼,這里就是GBK,而encodeURIComponent需要的是UTF-8編碼的字符,這樣它才會進行encode,所以必須發生編碼之間的轉換,具體如下,
“你好”的UTF-8:E4 BD A0 E5 A5 BD
所以“你好”實際上是以上面的6字節存儲在文件test.html里面(因為文件的實際編碼是UTF-8)
但是charset指定的GBK是2字節編碼(除128個ASCII外,都是2字節編碼),所以它把E4 BD認為是1個GBK字符,嘗試把它轉為UTF-8,其中E4 BD在GBK中對應的是“浣”字,而這個字在UTF-8則是E6 B5 A3
由此問題解決!
結論:如果網頁文件的文件編碼是UTF-8,而charset不小心指定成了其他的編碼,那么會發生編碼轉換,第一次是將原本以UTF-8編碼保存的字符當作charset指定的字符來讀取,然后再把它轉為UTF-8編碼。
再來看一個更加離奇的情況,現在有個test2.html文件,它的文件編碼是GBK,代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> console.log(encodeURIComponent("你")); //%EF%BF%BD%EF%BF%BD console.log(encodeURIComponent("你你")); //%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD console.log(encodeURIComponent("你你你")); //%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD </script> </head> <body> <p>hello</p> </body> </html>
“你”的GBK編碼是C4E3,UTF-8的EFBFDB表示這些字節信息無法轉為UTF-8對應的字符,現在我們來分析這個異常情況出現的本質原因:
C4的二進制是1100 0100,查看UTF-8編碼格式轉換表(百度百科的UTF-8詞條內就有這個表格),確實存在以110開頭的格式,以110開頭的字節會和它的下個字節組合為一個字符,而下個E3的二進制是11100 011,而UTF-8第二個字節開始都是10開始,顯然這時不匹配了,也就是說C4E3這個編碼是非法的UTF-8編碼,那么就會返回EFBFDB,由此問題解決。
對於文件本身是GBK編碼(或者其他非UTF-8編碼),且charset指定的編碼不是文件實際的編碼,那么這個方法會出現很多意料之外的行為,再比如,如下代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> console.log(encodeURIComponent("小")); //%D0%A1 console.log(encodeURIComponent("小小")); //%D0%A1%D0%A1 console.log(encodeURIComponent("小小小")); //%D0%A1%D0%A1%D0%A1 </script> </head> <body> <p>hello</p> </body> </html>
“小”的GBK編碼是D0A1,同理,D0的二進制是1101 0000,也存在以110開頭的UTF-8編碼,下個A1的二進制是1010 0001,和上個“你”字不同,這個確實是符合UTF-8第二字節均以10開頭的標准,那么這個字符是可以轉為UTF-8的,由此可以正常輸出。
最后再來看一個案例,文件依舊是GBK編碼,代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> console.log(encodeURIComponent("高")); //%EF%BF%BD%EF%BF%BD console.log(encodeURIComponent("高高")); //%EF%BF%BD%DF%B8%EF%BF%BD console.log(encodeURIComponent("高高高")); //%EF%BF%BD%DF%B8%DF%B8%EF%BF%BD </script> </head> <body> <p>hello</p> </body> </html>
“高”的GBK編碼是B8DF,而B8的二進制是1011 1000,呀,這個字節對於不了任何UTF-8起始字節,隨即返回EFBFBD編碼,示意這個字符無法對應(轉換)為UTF-8編碼,繼續看下個DF的二進制,它是1101 1111,110可以對應,而UTF-8中起始字節是110會和它的下個字節組合為一個字符,對於只有一個“高”來說,沒有下個字符了,單個的起始字符是不對應任何的UTF-8編碼,隨即也輸出EFBFBD,這就是console.log第一行輸出的結果,來看兩個“高”,剛才講到DF沒有下個字符了,所以出錯,現在有了第二個字符,是B8,那么就能組合了,因為B0的二進制以10開頭是符合的,剩下的字節同理啦。
總結:通常來說,網頁都采用UTF-8編碼,即文件編碼和charset指定的編碼要相同,enencodeURIComponent才能封裝和解析成功。
其實,只要文件編碼和給出的charset編碼相同,enencodeURIComponent就能正常工作,輸出為UTF-8字符序列。