最近在作表單需求的時候遇到了emoji的問題,發現本身對字符編碼上的理解仍是過於淺顯,因而翻閱資料,彌補缺陷。javascript
簡單來說,unicode
就是一個機器語音與人類語言的一張對應表。html
能夠理解爲一個很大很大的map
,惟一的key
對應惟一的字符。java
重要理念git
碼點指的是unicode
這個map
裏的每個key
。github
例如\u004a
對應的是J
這個字符,那麼J
的碼點就是74
數據庫
編碼是對碼點進行的編排,咱們平常說的UTF-8, UTF-16, UTF-32都是編碼方式。不一樣的編排方式有不一樣的優點與做用。後端
碼點範圍網絡
碼點從0x0000 - 0xFFFF,共可存放65536個字符frontend
碼點從0x010000 - 0x10FFFF,共可存放16 * 65536個字符,能夠劃分爲16個不一樣的平面函數
故咱們能夠認爲目前unicode碼錶的碼點最多能夠用4個字節來表示
Ascii碼你們都瞭解,128個字符,1個字節徹底能夠一一對應。可是計算機不是光美國使用,全世界人民都想用計算機,都想在計算機上顯示出本身的語言。因而就對這1個字節的剩餘128個空間開始搞事情。
那麼問題來了,中國文化博大精深,128個空間對於中國文字來說,簡直不夠塞牙縫。因而國人本身搞了一套GBK碼錶用兩個字節就能表示65536個字符了,暫時解決了一下問題。
可是碼錶與編碼不統一,必然會致使兩個計算機解碼顯示上的差別,也就形成了所謂的亂碼。
因而unicode的目的,就是爲了創建一張能容納全人類各類文化的字符的碼錶,來統一互聯網的字符規範。
經過編碼規則能夠對字符進行傳輸,經過解碼後的碼點與unicode表對照能夠正確的顯示文字
UTF-32
一種很簡單的定長編碼方式,均爲4個字節編碼
查找複雜度爲O(1)
浪費空間,傳輸內容只有ASCII時會是其的4倍。
故在網絡傳輸中,傳輸內容的大小決定了流量與客戶體驗,這種編碼方式並不適合互聯網。
UTF-16
一種變長的編碼方式,採用2個字節來表示基礎平面,4個字節來表示輔助平面
編碼範圍 | 字節 |
---|---|
0x0000 - 0xFFFF | 2 |
0x010000 - 0x10FFFF | 4 |
那麼在編碼與解碼過程當中,是如何區分應該是2字節仍是4字節呢?
咱們知道,輔助平面的字符共有 16 * 65536個,也就是 2^20個。
在基本平面內,碼點從0xD800到0xDFFF是一個空段,不對應任何字符。那麼正好從0xD800-0xDBFF空間大小爲1024(2^10),從0xDBFF-0xDFFF空間大小爲1024(2^10)。因而就能夠把輔助平面的碼點分爲高低位映射在這兩個碼點範圍內,正好能夠存儲全部的輔助平面字符。
也就是說只要超過0xFFFF的碼點,都會被編碼成高位在0xD800-0xDBFF,低位在0xDC00-0xDFFF的4字節碼點。這樣解碼的時候仍是依次讀取2字節,遇到0xD800-0xDBFF就知道這是個輔助平面字符,再讀取2字節去解碼
轉碼公式
H = Math.floor((c-0x10000) / 0x400)+0xD800 L = (c - 0x10000) % 0x400 + 0xDC00
UTF-8
一種變長的編碼方式,越經常使用的字符編碼長度越短
如何區分到底應該讀取幾個字節來編碼解碼呢?
answer
編碼範圍 | 編碼方式 | 字節 |
---|---|---|
0x0000 - 0x007F | 0xxxxxxx | 1 |
0x0080 - 0x07FF | 110xxxxx 10xxxxxx | 2 |
0x0800 - 0xFFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
0x010000 - 0x10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 4 |
編碼規則:
因爲UTF-8編碼這種變長的編碼規定,極大地節省了網絡傳輸數據的空間,傳輸經常使用的ASCII碼不用佔據額外的空間,提高了效率
有趣的是,js與採用的是UCS-2
編碼方法。
UCS-2
是UTF-16
編碼的子集,只支持2字節解析,從而就致使了4字節字符被當作2個2字節字符解析,字符函數等就會出現問題。
question
// '𝌆'碼點是0x1d306,在輔助平面內,按照UTF-16編碼規則,會解析爲高低位的2個2字節字符 '𝌆'.length // 2 '𝌆'.charCodeAt(0) // 55348 0xd834 '𝌆'.charCodeAt(1) // 57094 0xdf06 '\ud834\udf06' === '𝌆' // true '\u1d306' // ᴰ6 // 實際上只解析了2字節,把6當作了字符6,故不能正常按unicode直接解析出正確的字符 '\u1D306' === '\u1D30' + '6' // true // 那麼天然 調用split等字符串處理函數必然會出現問題 '123𝌆'.split('').reverse().join('') // 非 𝌆321 而是 ��321
answer
ES6爲支持4字節字符提供了更多的解決方案
for of 能夠徹底正確地遍歷字符串 Array.from(str).length 能夠獲得正確的字符數 '𝌆' === '\u{1d306}' // true 經過碼點能夠直接使用unicode表示輔助平面字符了 String.fromCodePoint(0x1d306) // 經過碼點獲得字符 '𝌆'.codePointAt(0) // 119558 直接得到正確的碼點 /^.$/u.test('𝌆') // true 能夠正常當作1個字符來檢測了
若是不支持ES6的話,只能老老實實作一些判斷嘍。
emoji
經過上面的知識,不難猜想emoji也就只是unicode某個輔助平面裏碼點對應的一個字符而已
'😊'.codePointAt(0) // 128522 '😊'.length // 2 '\ud83d\ude0a' === '😊' // true
不過發送到後端存儲的時候若是數據庫是utf8編碼的話會有一些問題,須要後端輔助設置一下數據庫編碼。否則的話咱們只能想辦法替換一下,而後接收數據後再替換回來 仍是挺麻煩的。
顏文字
'(ง •̀_•́)ง'.length // 10 // 看上去只有8個字符,而length倒是10,循環輸出一下,發現問題在'•̀'上面 '•̀'.length // 2 '\u2022\u0300' === '•̀' // \u0300 是聲調類符號,能夠顯示在前一個字符的某些位置上 // 我以爲這個表情還不夠能給人鼓勵。。因而。。 "\u0028\u0e07\u0020\u2022\u0300\u0300\u0300\u005f\u2022\u0301\u0301\u0301\u0029\u0e07\u0301\u0301\u0301" === "(ง •̀̀̀_•́́́)ง́́́" // 哈哈 是否是還蠻有意思的 // 有創造力的能夠自行給本身的產品建立屬於本身的顏文字了哈哈哈哈哈哈
瞭解unicode加深了我對字符串的理解和對亂碼緣由的理解,對平常開發仍是狠狠狠有幫助的,尤爲是對一些須要emoji的表單就要從開發時候作一些特殊的處理啦。
若是喜歡能夠star一下,會不斷更新github地址