前一陣做活動有一個分享文案總是分享錯誤,排除法之后發現是一個字符的編解碼問題。好幾次遇到這種問題都是懵過去的,這次有時間寫篇文章大概梳理一下。
字符集?字符編碼?
首先我們知道,計算機能接收的指令和能處理的信息都最后都是要轉化成二進制的數據的。而在很早以前,我們需要處理的是文本,這就衍生了 ASCII 字符集,規定了打印字符(例如大寫小寫英文字母、數字,和一些其他字符),控制字符(例如回車,上下左右)和編號的對應關系,這個編號就是像 URI 一樣給字符的唯一標識。而字符編碼則是根據這個編號,對應寫入字節流。
為了方便大家理解,放出一個 ASCII 表再熟悉一下:
我們后面說到的表的原理都大同小異,就是把一個獨一無二的編號和一個字符對應起來,而字符編碼的過程就是讓計算機拿到這個編號之后知道這個東西應該展示/存儲成什么樣子。
但是我們剛才說到的字符集是基于單字節編碼,什么叫單字節編碼?看到上圖的左上角低四位/高四位沒?一共8位即一個字節就可以表示一個字符,這就是單字節編碼。掐指一算8位表示一個字符,這一共也才能表示256個字符啊,太少了!發現 ASCII 字符集遠遠不夠用,多用幾位表示不就行了?所以有了多字節編碼方式。例如中國使用的就是雙字節字符編碼(使用最多兩個字節來編碼),并且我們有自己的字符集 GB2312,GBK 是在前者的基礎上增加了繁體字等的編碼。
到此為止,我們的字符集都不能包含人類所用的全部字符。然后就衍生了 Unicode 字符集。它為每個字符統一編號,分配唯一的字符碼。
上面字符集和編碼方式是直接綁定在一起的,想擴展是很復雜的事情;而 unicode 是要涵蓋所有字符,包括未來不可預知的字符。為了提高可擴展性,我們把字符集和字符編碼分開,雖然 編號(unicode 碼)相同,但是最終決定字節流的是你的字符編碼方案。舉幾個我們相當熟悉的字符編碼,UTF-8和 UTF-16,見圖。
所以說到這里其實我們也知道了,unicode 編碼和 unicode 字符集不是一回事。 unicode 編碼是能對 unicode 字符碼轉換成字節流的字節編碼方式的統稱,包含 urf-8等實現方式。
關于亂碼?
我不知道你在說什么,就沒法解析你的話。我用codepage936的字符編碼去解你用 utf-8編碼出的字節流,自然不知道你在說什么。
網頁指定字符編碼的位置一般就是,后端返回的響應頭的 content-type;網頁 meta:http-equiv;
在 Javascript 中的應用?
我能想到的就一下這些方法,大家可以補充。
encodeURI:是按 utf-8 進行編碼,除了:; , / ? : @ $ = + &
(前面這些是保留字符,保留的原因不再贅述) 字母 數字 _ - · ! ~ * ' ( ) #
encodeURIComponent:也是按 utf-8 進行編碼,除了:字母 數字 _ - · ! ~ * ' ( ) #
btoa:將 ASCII 字符串或者二進制數據轉為 base64編碼過的字符串,該方法不能直接作用于 Unicode 字符串
atob:反過來
JSON.parse: 經歷兩次轉義。第一次調用 toString()方法(注意在這個方法中已經對內部的\n 等進行一次轉義),第二次把JSON字符串轉義為 JSON 對象。
eval:同上,并且和 parse 一樣可以把符合 json 標準的字符串轉化為 json 對象,但是因為設計安全問題所以不推薦使用
有時候直接 console 是對的,但是放在 html 不對,是因為 console 其實是自帶一次 toString()的過程的,而 toString()會轉義。
反斜線到底什么用?首先是展示一些不能展示的ASCII字符,例如回車\n
,tab鍵\t
等。其次是展示一些特殊用途的字符,雙引號\"
等等。本質上他的作用是替代一些不能表示的東西。
有時候反斜線展示在頁面上,不能成功顯示為應該顯示的字符,是因為沒有經歷解碼的過程。
為什么在 控制臺的HTML 里直接改成\n 不能用,因為直接在控制臺里寫是不經過轉義的。。
為啥innertext="\n"顯示結果是回車,innerHTML="\n"顯示結果不是回車(也不直接展示\n,因為這兩個方法都會先轉義)?因為前者是把你給他的東西做基礎轉義之后原封不動的放進去,而后者會經過一次 HTML 轉義,你在代碼里直接寫回車也不會搭理你的。。。(是 HTML 文本中空白符的默認處理方式,可以修改,比如你 css 中的 white-space
)
下面貼一段轉義的定義(來自百度百科):
轉義字符串(Escape Sequence)也稱字符實體(Character Entity)。在HTML中,定義轉義字符串的原因有兩個:第一個原因是像“<”和“>”這類符號已經用來表示HTML標簽,因此就不能直接當做文本中的符號來使用。為了在HTML文檔中使用這些符號,就需要定義它的轉義字符串。當解釋程序遇到這類字符串時就把它解釋為真實的字符。在輸入轉義字符串時,要嚴格遵守字母大小寫的規則。第二個原因是,有些字符在ASCII字符集中沒有定義,因此需要使用轉義字符串來表示。
其實所有編程語言,擁有轉義字符的原因基本上是兩點:一、使用轉義字符來表示字符集中定義的字符,比如ASCll里面的控制字符及回車換行等字符,這些字符都沒有現成的文字代號。所以只能用轉義字符來表示 。二、某一些特定的字符在編輯語言中被定義為特殊用途的字符。這些字符由于被定義為特殊用途,它們失去了原有的意義。比如說HTML中,<被HTML定義為標簽的開始,所以當我們轉入<時,HTML就會把它當作為開始,而不是當作一個<來看待。再如PHP 的雙引號("),被PHP定義為字符串的外圍標簽,所以如果你在一對雙引號里面,還想要使用雙引號,只能使用轉義字符了。不然PHP就會報錯了。
從上面也可以看出轉義無非是兩種情況:1:將普通字符轉為特殊用途,一般是編程語言中,用于表示不能直接顯示的字符,比如后退鍵,回車鍵,等。2:用來將特殊意義的字符轉換回它原來的意義。一般用在正則表達式中。還有有些腳本語言是弱類型,有些語言比如HTML 并不是編程語言,而是標記語言,有些語言只有一種類型 比如shell 腳本語言,這些語言中字符串都不加引號” ” ,或者可以不加引號“ ”,所以有時候需要轉義字符說明某字符此時的身份是普通字符,而不是有特殊意義的元字符。
base64?用武之地?編碼過程?
是一種基于64個可打印字符來表示二進制數據的方法,是用于傳輸8bit 字節碼的編碼方式之一。
是從二進制到文本數據的過程。
具有不可讀性,需要解碼后才能閱讀。
因為 base64可以表示所有二進制數據,所以基本上意味著所有數據都可以轉換成 base64的格式。
拿我們最熟悉的 ASCII 舉例子,我們知道在 ASCII 碼中,128~255是不可見字符。而這些字符在傳輸過程中,如果直接傳輸會在中間過程被處理錯誤,且這個過程是不可逆的(原有的字符會被替換丟失),所以我們要保證傳輸過程中沒有不可見字符,讓數據符合傳輸協議的要求。相當于 base64就像我們為了數據傳輸準確而帶的通行證。
但是注意 base64編碼過后的數據大概會編程原來的4/3,因為有填補空位的過程。
我們常用的場景就是,圖片傳輸,根證書,郵件附件等。
編碼過程:對于非二進制數據,是先將其轉換成二進制形式,然后每連續6比特(2的6次方=64)計算其十進制值,根據該值在上面的索引表中找到對應的字符,最終得到一個文本字符串。
%是啥意思?為啥 encode 完了都帶上這個,但是明明字符編碼完不是這樣的?
而且為啥我在 HTML 里直接寫\n 不行?
在 c 語言中,轉義用\
表示,在 URI 協議中,轉義字符是百分號%
。并且其他語言的轉義字符可能各不相同。
這也就解答了為什么你直接在HTML 里用\n
是不行的,因為HTML 中轉義字符不是\
,他只當這是兩個正常的字符,一個反斜線一個字母 n。只是在 Javasciprt 這門語言里(當然還有很多其他語言例如 C)會轉義成回車。
參考文章:
https://segmentfault.com/q/1010000009768523/a-1020000009774588
https://stackoverflow.com/questions/12694110/rendering-newlines-in-escaped-html