js 傳遞漢字 亂碼_JavaScript 字串反轉亂碼問題解決
技術標籤:js 傳遞漢字 亂碼
JavaScript 字串反轉亂碼
前一段寫了篇文章:JavaScript 8種字串反轉的方法 – 碼中人,介紹了js中反轉字串的幾個方法。
但有些字串或有些方法無法達到預期結果,如:
emoji字元
"Trump ".split("").reverse().join("");// "�� pmurT"
生僻漢字
"野家".split("").reverse().join("");// "家野��"
複合字符
"noël".split("").reverse().join("");//"l̈eon"
字元逆轉雖然沒有出現亂碼,但原本字母”e”上面修飾符,變成了”l”的修飾符。
產生亂碼的原因
以上三例子的錯誤是在哪個步驟產生的?先來看string.split()之後:
顯然在split()方法就已經出錯了,並且 長度為n的字串,轉化成了n+1長度的陣列。
本質原因要回溯到javascript字元編碼的方式。
Unicode與JavaScript詳解 – 阮一峰的網路日誌
JavaScript語言採用Unicode字符集,最初使用的字元編碼是UCS-2!
但UCS-2 編碼方式只覆蓋基本多語言平面(BMP)的碼點,因為 16 位二進位制表示的最大值為 0xFFFF,而對於增補平面中的碼點(範圍為 0x10000~0x10FFFF,十進位制為 65536~1114111),兩位元組的 16 位二進位制是無法表示的。為了解決這個問題,The Unicode Consortium 提出了通過代理對(surrogate pair)機制來擴充套件原來的 UCS-2 編碼方式,也就是 UTF-16。
可以這麼理解,JavaScript字元編碼是 UCS-2 + 代理對(surrogate pair)。
代理對規定用兩個16位編碼單元來表示一個碼位(超出BMP的碼位),該實現方式相對有些複雜,本文不贅述。
因為代理對,JavaScript字串裡的字元有兩種:
- 由一個碼元(16位)表示的BMP字元
- 由兩個碼元(16*2位)表示的輔助平面字元
這就給 JS 中的 Unicode 處理帶來了很多問題,基本上所有的字串操作函式在處理非 BMP 字元時都是錯誤的。
“”與“”這兩個字元都是通過代理對錶示的,有2個碼元長度,所以js把它們當成2個字元,長度變成n+1。同時這兩個單獨的碼元所指向的碼點上沒有相應字元,變成了亂碼。
解決方法
1 ES6 自動識別
ES6 中的for of 、array.from(str)、… 都可以自動識別兩個碼元字元,把它當成一個字元。
[... "Trump "].reverse().join("");" pmurT"
Array.from("野家").reverse().join("");"家野"
2 引入ssrever 類庫
mathiasbynens/esrever: A Unicode-aware string reverser written in JavaScript.
var input = 'Lorem ipsum dolor sit ameͨ͆t.';var reversed = esrever.reverse(input);console.log(reversed);// → '.teͨ͆ma tis rolod muspi meroL'esrever.reverse(reversed) == input;// → true
複合字符
但是”noël”的逆轉還是不成功。
Array.from("noël").reverse().join("");"l̈eon"
這個問題和 JS 沒關係,是 Unicode 字符集本身的問題。
根據 Unicode 定義,有些字元屬於修飾字符,也就是和別的字元一起出現的時候會修飾別的字元,兩個合在一起構成一個我們人眼中的字元。
比如,ë 這個字元,由兩個 Unicode 碼點構成,分別是 U+0065 和 U+0308。這兩個都是 Unicode 中的合法字元,擁有自己的碼點,但他們合在一起的時候,構成一個我們人類眼中的字元。
同時,在 Unicode 中,還有一個單獨的字元 ë,碼點為 U+00EB。
ë 和 ë 在我們眼中是一樣的字元,但在 Unicode 中卻是不同的表現,一個是由兩個字元拼接而成,另一個是獨立的字元,因此,如果直接比較的話,肯定是不相等的。
"ë" === "ë" // false
這時候就需要引入規整化,將字元轉變為某種特定的形式。Unicode 中定義了四種形式,常用的兩種是:
- NFD: Normalization Form Canonical Decomposition,將所有的單個的複合字符轉換為多個字元拼接而成的形式
- NFC: Normalization Form Canonical Composition,將所有的拼接而成的符合字元轉換為單個字元的形式
因此,在比較 Unicode 字串之前,我們需要對兩邊的字串規整化到相同的形式,這樣結果才是準確的。ES6 中引入的 String.prototype.normalize 方法可以用於字串的規整化。
"ë".normalize("NFC") === "ë".normalize("NFC") // true
所以,正確反轉“noël”需要先將其規整化。
Array.from("noël".normalize()).reverse().join("");"lëon"
參考資料
- Unicode與JavaScript詳解 – 阮一峰的網路日誌
- ES6走走看看—字元到底發生了什麼變化 – 掘金
- Unicode 及編碼方式概述 – IBM Developer