關於js漢字編碼的問題

問題出現
在項目中遇到問題
「一二三四五?六七八九十」,被識別爲10個字。
1、2、...、十 。呃...彷佛有點問題。應該是11個字的啊。
問題就出在?,這個字沒有被識別。javascript

問題分析
看看代碼:html

JavaScript容許直接用碼點表示Unicode字符,寫法是"反斜槓+u+碼點"。碼點有十六機制數表示。
可是,這種表示法對4字節的碼點無效。ES6修正了這個問題,只要將碼點放在大括號內,就能正確識別。

根據漢字unicode範圍表發現經常使用的大多數漢字均可用u4E00-u9FA5來表示。而?的unicode碼是U+20BB7,沒有被包含。因此,咱們須要匹配漢字unicode範圍表全部的漢字。java

可是, 編碼相似u20BB7的4字節的碼點不能直接被識別。咱們須要理解一下js使用的編碼。
JavaScript使用哪種編碼?
JavaScript用的是UCS-2!git

UCS的開發進度快於Unicode,1990年就公佈了第一套編碼方法UCS-2,使用2個字節表示已經有碼點的字符。(那個時候只有一個平面,就是基本平面,因此2個字節就夠用了。)UTF-16編碼遲至1996年7月才公佈,明確宣佈是UCS-2的超集,即基本平面字符沿用UCS-2編碼,輔助平面字符定義了4個字節的表示方法。

因爲JavaScript只能處理UCS-2編碼,形成全部字符在這門語言中都是2個字節,若是是4個字節的字符,會看成兩個雙字節的字符處理。JavaScript的字符函數都受到這一點的影響,沒法返回正確結果。
unicodegithub

這麼多符號,Unicode不是一次性定義的,而是分區定義。每一個區能夠存放65536個(216)字符,稱爲一個平面(plane)。目前,一共有17個(25)平面,也就是說,整個Unicode字符集的大小如今是221。
最前面的65536個字符位,稱爲基本平面(縮寫BMP),它的碼點範圍是從0一直到216-1,寫成16進制就是從U+0000到U+FFFF。全部最多見的字符都放在這個平面,這是Unicode最早定義和公佈的一個平面。
剩下的字符都放在輔助平面(縮寫SMP),碼點範圍從U+010000一直到U+10FFFF。

UTF-16正則表達式

究竟是把這兩個字節看成一個字符仍是與後面的兩個字節一塊兒看成一個字符呢? 這裏有一個很巧妙的地方,在基本平面內,從 U+D800 到
U+DFFF 是一個空段,即這些碼點不對應任何字符。所以,這個空段能夠用來映射輔助平面的字符。 輔助平面的字符位共有 220220
個,所以表示這些字符至少須要 20 個二進制位。UTF-16將這 20 個二進制位分紅兩半,前 10 位映射在 U+D800 到
U+DBFF,稱爲高位(H),後 10 位映射在 U+DC00 到
U+DFFF,稱爲低位(L)。這意味着,一個輔助平面的字符,被拆成兩個基本平面的字符表示。 所以,當咱們遇到兩個字節,發現它的碼點在
U+D800 到 U+DBFF 之間,就能夠判定,緊跟在後面的兩個字節的碼點,應該在 U+DC00 到 U+DFFF
之間,這四個字節必須放在一塊兒解讀。

總的來講,一個輔助平面的字符,被拆成兩個基本平面的字符表示。或者在ES6中能夠用'u{20BB7}'來表示。函數

ES6的支持編碼

  • ES6能夠自動識別4字節的碼點。所以,遍歷字符串就簡單多了。

    for (let s of string ) { // ...
    }prototype

爲了獲得字符串的正確長度,能夠用下面的方式。code

Array.from(string).length   

[...string].length
  • 碼點表示法,JavaScript容許直接用碼點表示Unicode字符,寫法是"反斜槓+u+碼點"。可是,這種表示法對4字節的碼點無效。ES6修正了這個問題,只要將碼點放在大括號內,就能正確識別。
  • String.fromCodePoint():從Unicode碼點返回對應字符
  • String.prototype.codePointAt():從字符返回對應的碼點
  • String.prototype.at():返回字符串給定位置的字符
  • 正則表達式,ES6提供了u修飾符,對正則表達式添加4字節碼點的支持。

問題解決
因此,咱們在正則中表示全部的漢字,須要將不能被直接識別的4字節識別,能夠經過ES6的方式,也能夠轉換成基本平面來表示。咱們須要將全部的漢字區間都包含在匹配公式中,從上面的漢字unicode範圍表看,本身來手寫是很複雜。
還好,有Regenerate能夠來完成這件事情。經過它咱們能夠快速的表示出複雜的正則表達式。

export const character2unicode = regenerate()
  .addRange(0x4e00, 0x9fa5)
  .addRange(0x9fa6, 0x9fcb)
  .addRange(0x3400, 0x4db5)
  .addRange(0x20000, 0x2a6d6)
  .addRange(0x2a700, 0x2b734)
  .addRange(0x2b740, 0x2b81d)
  .addRange(0x2f00, 0x2fd5)
  .addRange(0x2e80, 0x2ef3)
  .addRange(0xf900, 0xfad9)
  .addRange(0x2f800, 0x2fa1d)
  .addRange(0xe815, 0xe86f)
  .addRange(0xe400, 0xe5e8)
  .addRange(0xe600, 0xe6cf)
  .addRange(0x31c0, 0x31e3)
  .addRange(0x2ff0, 0x2ffb)
  .addRange(0x3105, 0x3120)
  .addRange(0x31a0, 0x31ba)
  .toRegExp();

// character2unicode 
// /[\u2E80-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3105-\u3120\u31A0-\u31BA\u31C0-\u31E3\u3400-\u4DB5\u4E00-\u9FCB\uE400-\uE5E8\uE600-\uE6CF\uE815-\uE86F\uF900-\uFAD9]|[\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/

以上就能夠表示出漢字unicode範圍編碼的正則。感受能夠很愉快的開工。

參考
https://mathiasbynens.be/note...
http://www.ruanyifeng.com/blo...
https://github.com/mathiasbyn...

相關文章
相關標籤/搜索