從0開始構建本身的前端知識體系-有趣的unicode

前言

最近在作表單需求的時候遇到了emoji的問題,發現本身對字符編碼上的理解仍是過於淺顯,因而翻閱資料,彌補缺陷。javascript

What is unicode

簡單來說,unicode就是一個機器語音與人類語言的一張對應表。html

能夠理解爲一個很大很大的map,惟一的key對應惟一的字符。java

  • 重要理念git

    • 碼點

      碼點指的是unicode這個map裏的每個keygithub

      例如\u004a對應的是J這個字符,那麼J的碼點就是74數據庫

    • unicode與編碼

      編碼是對碼點進行的編排,咱們平常說的UTF-8, UTF-16, UTF-32都是編碼方式。不一樣的編排方式有不一樣的優點與做用。後端

    • 碼點範圍網絡

      • 基礎平面

        碼點從0x0000 - 0xFFFF,共可存放65536個字符frontend

      • 輔助平面

        碼點從0x010000 - 0x10FFFF,共可存放16 * 65536個字符,能夠劃分爲16個不一樣的平面函數

故咱們能夠認爲目前unicode碼錶的碼點最多能夠用4個字節來表示
  • history

    Ascii碼你們都瞭解,128個字符,1個字節徹底能夠一一對應。可是計算機不是光美國使用,全世界人民都想用計算機,都想在計算機上顯示出本身的語言。因而就對這1個字節的剩餘128個空間開始搞事情。

    那麼問題來了,中國文化博大精深,128個空間對於中國文字來說,簡直不夠塞牙縫。因而國人本身搞了一套GBK碼錶用兩個字節就能表示65536個字符了,暫時解決了一下問題。

    可是碼錶與編碼不統一,必然會致使兩個計算機解碼顯示上的差別,也就形成了所謂的亂碼。

    因而unicode的目的,就是爲了創建一張能容納全人類各類文化的字符的碼錶,來統一互聯網的字符規範。

unicode 編碼規則

經過編碼規則能夠對字符進行傳輸,經過解碼後的碼點與unicode表對照能夠正確的顯示文字

  • UTF-32

    一種很簡單的定長編碼方式,均爲4個字節編碼

    • 優勢

      查找複雜度爲O(1)

    • 缺點

      浪費空間,傳輸內容只有ASCII時會是其的4倍。

故在網絡傳輸中,傳輸內容的大小決定了流量與客戶體驗,這種編碼方式並不適合互聯網。

  • UTF-16

    一種變長的編碼方式,採用2個字節來表示基礎平面,4個字節來表示輔助平面

    編碼範圍 字節
    0x0000 - 0xFFFF 2
    0x010000 - 0x10FFFF 4
    • question

      那麼在編碼與解碼過程當中,是如何區分應該是2字節仍是4字節呢?

    • answer

      咱們知道,輔助平面的字符共有 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字節去解碼

    • 轉碼公式

      1. 若是是基本平面字符,直接將碼點轉爲對應的16進制形式,長度爲2字節
      2. 若是是輔助平面字符,則使用轉碼公式
      H = Math.floor((c-0x10000) / 0x400)+0xD800
      
      L = (c - 0x10000) % 0x400 + 0xDC00
  • UTF-8

    一種變長的編碼方式,越經常使用的字符編碼長度越短

    • question

      如何區分到底應該讀取幾個字節來編碼解碼呢?

    • answer

      編碼範圍 編碼方式 字節
      0x0000 - 0x007F 0xxxxxxx 1
      0x0080 - 0x07FF 110xxxxx 10xxxxxx 2
      0x0800 - 0xFFFF 1110xxxx 10xxxxxx 10xxxxxx 3
      0x010000 - 0x10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4

      編碼規則:

      1. 對於碼點在0-127的,用1字節,第一位置0,後面按碼點對應,其實就與ASCII相同了
      2. 對於n字節的符號(n > 1),第一個字節的前n位都設爲1,第n + 1位設爲0,後面字節的前兩位一概設爲10。剩下的沒有說起的二進制位,所有爲這個符號的 Unicode 碼。
因爲UTF-8編碼這種變長的編碼規定,極大地節省了網絡傳輸數據的空間,傳輸經常使用的ASCII碼不用佔據額外的空間,提高了效率

JavaScript編碼規則

有趣的是,js與採用的是UCS-2編碼方法。

UCS-2UTF-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的話,只能老老實實作一些判斷嘍。

有趣的unicode

  • 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地址

相關文章
相關標籤/搜索