每日 30 秒 ⏱ 字元編碼排雷錄

zhangxiangliang發表於2019-03-25

簡介

? 更新平臺多偶爾會漏掉,如果覺得文章還行點個 star 防走失。

? 一天 30 秒 ⏱ 一段程式碼 ✍️ 一個場景 ? ?

計算機重重底層之下都是由 0 和 1 組合,但是你知道他們是怎麼一步步變成字串的嘛?在我們現實生活中最常見的例子可以通過 wo 在新華字典中找到 這個字。同樣計算機通過 0 和 1 組合在 字典 中查詢到對應的字元,那 字典 內容是什麼呢?

起源

計算機誕生於 美國 它的使用者大多數使用英文,美國國家標準學會 便制定了這本字典包括了 26個大寫英文字母26個小寫英文字母10個阿拉伯數字等總共 256 個字元的 ASCII 字符集。

混亂

ASCII 用二進位制來表示就是 0000 00001111 1111 被用得滿滿當當,漢字就沒有地方可以放得下了這下怎麼辦?正所謂江山大有人才出,國標編碼 GB 系列出現了,其中最耳熟能詳的就是 GB2312

那麼問題來了世界擁有 25003500 種語言,有文字的語言有 930 種。你能想象你在瀏覽不同語言介面的時候,需要自己不斷的去切換 字典 並且 每次切換查詢不到的字元就會亂碼出現。

統一

書同文,車同軌,行同倫。

上面這句話歌頌了秦始王具有跨時代意義的成就,但是現實世界中統一語言顯得不可能。那我們能否換個思路解決這個問題呢?先思考一個問題:“把大象放入冰箱需要幾步”,答案大家都知道“開啟,裝進去,關上”。那統一字元怎麼辦呢?那就是建立一個足夠大的字典把所有的字元都放進去。

萬國碼

Unicode 萬國碼 轟隆一聲誕生了,顧名思義統一了全世界的所有文字編碼可以到 Unicode Consortiumcodepoints 中檢視,對應的實現有UTF8、UTF16 、UTF-32。

可變長度字元編碼

UTF8、UTF16、UTF-32最大區別在於對應多少位元組的資料,越大能儲存的字元也就越多。其中 UTF-8 就是在網際網路上使用最廣的一種 Unicode 的實現方式,也就是現在 html 中最常看到的 <meta charset="UTF-8"> 所宣告字符集。

UTF 最大的特點在於可變長的位元組,例如 UTF8 可以是 1到4個位元組來記錄 萬國碼,為什麼這麼設計呢?日常使用得到的字元對應的字元編碼沒有必要佔用這麼多位元組,例如 0000 00000000 0000 0000 0000 都能表示 0,那使用更短的位元組所佔用的空間更小,傳輸的速度更快。

小插曲

在統一編碼的過程中還出現了一個字符集 UCS-2,它固定使用 2個位元組來編碼 與 UTF 可變長度字元編碼有一定程度上的不同,但是隨著統一程式下被 UTF-16 收編了。

JavaScript 字元處理

瞭解字元基本原理和程式後,那麼 JavaScript 是什麼編碼呢?沒錯它就剛才 小插曲 中提到的 UCS-2,原因是 JavaScript 誕生時 UTF-16 還沒有出現。

但是現在大家都在使用 UTF 可變長度字元編碼UTF-16 的可變位元組為 2個或者 4個,而 UCS-2 卻只有 2個。這樣兩個字符集之間就有存在一個 UCS-2 無法識別的 4位元組字元,JavaScript 在處理字元時會傻傻的按著 UCS-2 的兩位元組去處理,再加上字典裡沒有這個字元笨笨的小腦袋瓜無法處理只能輸出亂碼。

由於 emoji 表情的普及,而且 emoji 剛好就是處於 UCS-2字典之外,在前端開發中遇到可能出現 emoji 的地方需要小心謹慎:

長度

BUG 預警

現在最為常用的 emoji 表情為 4個位元組編碼表示,由於 UCS-2 固定兩個位元組,在統計長度時 emoji 會被當做兩個 UCS-2 字元,結果會和我們預期的輸出大了一倍。

let emoji = "?";

// 輸出 2
console.log(emoji.length);
複製程式碼
BUG 解除

利用 es6 的 Array.prototype.fromspread 來做字串轉陣列並計算長度:

let emoji = "?";

// 輸出 1
console.log(Array.from(emoji).length);

// 輸出 1
console.log([...emoji].length);
複製程式碼

如果不支援 Array.prototype.from 可以利用正則替換把 4位元組的字元替換為 _ 並計算長度:

let emoji = "?";

function countSymbols(string) {
    var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
    return string
        .replace(regexAstralSymbols, '_')
        .length;
}

// 輸出 1
console.log(countSymbols(emoji));
複製程式碼

對於其他的字串操作,例如拼接或者替換也可以利用陣列來實現。

反轉字串

如同上面所講 emoji 會被當做兩個 UCS-2 字元,反轉的時候 4個完整的位元組會被硬生生的拆分開來,可以使用 Esrever 來解決。

let emoji = "?";

// 輸出為兩個亂碼字元
console.log(emoji.split('').reverse().join(''));
複製程式碼

字元編碼轉換

在使用 String.prototype.charCodeAtString.fromCharCode 會出現問題。可以使用 ES6 的兩個新方法來替換 String.prototype.codePointAtString.fromCodePoint

正則匹配

正則裡 . 表示匹配一個字元,但是 UTF-16 4位元組字元會被當做兩個字元來處理,進而引起錯誤。ES6 給出了新的解決方法加上 u 標誌 /./u.text('?'),所以寫正則的時候要記得加上哦。

字串遍歷

對於字串的遍歷可以使用 for...of 語句。

場景

如果後端資料庫執行儲存 emoji 作為使用者名稱時,前端在限制使用者名稱長度判斷時需要注意UTF-16 4位元組字元帶來的統計錯誤,其他類似場景同理可得。

小提示:在做微信公眾號開發時,由於使用者名稱和使用者輸入可能出現 emoji 等字元,需要對資料庫進行字符集的設定。

不要問我為什麼知道,因為我的眼裡常含淚水。

一起成長

如果您感覺有收穫可以點贊關注激勵我,也歡迎到 Github 加個 star。

微信公眾號

本文原稿來自 PushMeTop

相關文章