成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

了不起的Unicode

開發(fā) 前端
在2000多年前,我們那迷人的老祖宗,秦始皇,就實(shí)現(xiàn)了「車同軌,書同文」,劃破「地域障礙」,從而給不同地方的人在交流上開辟了新的空間。雖然,有些地方還存在「十里不同音,百里不通俗」的情況(我老家山西就是這種情況)。但是,在官方層面或者書面層面上,大家可以溝通無阻。

前言

提出一個小小的問題。大家按照自己的開發(fā)語言的特性,想想結(jié)果是啥?

"????♂?"這個Emoji的長度是多少?

如果,現(xiàn)在你用電腦閱讀本文,你可以輕松的打開xx PlayGround(xx可以為Js/Java/Rust等)。然后會得到屬于自己語言的結(jié)果。

如果,你現(xiàn)在手頭沒電腦,無法親自驗(yàn)證,我來直接告訴你答案。上述Emoji在每種語言環(huán)境下的結(jié)果都不統(tǒng)一。(當(dāng)然,有些語言內(nèi)核使用的機(jī)制一樣,結(jié)果可能也一樣)。

也就是說,在編程層面,這不是一種 「所見即所得」的表現(xiàn)形式。大家這里可能會納悶了,我要知道這個有啥?現(xiàn)在舉一個例子,在前端頁面中,我們總是會有統(tǒng)計用戶字?jǐn)?shù)的輸入框,但是由于用戶輸入了Emoji,從用戶的角度來看,這就是一個字符,但是在編程層面,如果不做一次解析的話,我們會得到千奇百怪的答案。

然后,我們再來一個讓人匪夷所思的例子。在瀏覽器中,嘗試復(fù)制如下代碼,然后進(jìn)行觀察答案。結(jié)果是不是又再一次顛覆你的所學(xué)。

"A?" === "?";

平時,我們時不時的會提到UTF-8/UTF-16/UTF-32它們到底是個啥?又有啥關(guān)系和區(qū)別呢?

還有其他的例子就不一一列舉了。之所以會出現(xiàn)這么多讓人匪夷所思的結(jié)果。一切的根源都是Unicode的鬧的。

所以,今天我們就來談?wù)勥@是何方神圣。

在2000多年前,我們那迷人的老祖宗,秦始皇,就實(shí)現(xiàn)了「車同軌,書同文」,劃破「地域障礙」,從而給不同地方的人在交流上開辟了新的空間。雖然,有些地方還存在「十里不同音,百里不通俗」的情況(我老家山西就是這種情況)。但是,在官方層面或者書面層面上,大家可以溝通無阻。

好了,天不早了,干點(diǎn)正事哇。

我們能所學(xué)到的知識點(diǎn)

  1. 前置知識點(diǎn)
  2. Unicode 是個啥?
  3. UTF-8 又是什么?
  4. UTF-32 問題
  5. Unicode 病癥
  6. 如何檢測擴(kuò)展形素簇
  7. "A?" !== "?" !== "?"
  8. Unicode 取決于區(qū)域設(shè)置

1. 前置知識點(diǎn)

「前置知識點(diǎn)」,只是做一個概念的介紹,不會做深度解釋。因?yàn)椋@些概念在下面文章中會有出現(xiàn),為了讓行文更加的順暢,所以將本該在文內(nèi)的概念解釋放到前面來。「如果大家對這些概念熟悉,可以直接忽略」同時,由于閱讀我文章的群體有很多,所以有些知識點(diǎn)可能「我視之若珍寶,爾視只如草芥,棄之如敝履」。以下知識點(diǎn),請「酌情使用」。

ASCll

ASCII[1](American Standard Code for Information Interchange)的縮寫,發(fā)音為ask-key。ASCII是一種用于表示字符的7位標(biāo)準(zhǔn)編碼,其中包括字母、數(shù)字和標(biāo)點(diǎn)符號。

圖片圖片

7 位編碼允許計算機(jī)編碼總共128個字符,包括數(shù)字 0-9、大寫和小寫字母 A-Z 以及一些標(biāo)點(diǎn)符號。然而,這 128 位編碼僅適用于英語用戶。

ASCII 的功能

  1. ASCII的建立旨在實(shí)現(xiàn)各種數(shù)據(jù)處理設(shè)備之間的「兼容性」,從而使這些組件能夠成功地相互通信。
  2. ASCII使制造商能夠生產(chǎn)可以確保在計算機(jī)中正確運(yùn)行的組件。
  3. ASCII使人機(jī)互動。

ASCII 在計算機(jī)系統(tǒng)中的工作原理

當(dāng)我們按下鍵盤上的鍵,例如字母D時,電子信號被發(fā)送到計算機(jī)的CPU進(jìn)行處理和存儲在內(nèi)存中。「每個字符都被轉(zhuǎn)換為其對應(yīng)的二進(jìn)制形式」。計算機(jī)將字母處理為一個字節(jié),實(shí)際上是一系列電子狀態(tài)的開和關(guān)。當(dāng)計算機(jī)完成處理字節(jié)后,系統(tǒng)中安裝的軟件將字節(jié)轉(zhuǎn)換回,并在屏幕上顯示。字母 D 被轉(zhuǎn)換為01000100。

TextEncoder 和 TextDecoder

TextEncoder 和 TextDecoder 是 JavaScript 中用于處理字符編碼的「內(nèi)置對象」。它們通常用于在不同字符編碼之間進(jìn)行文本的編碼和解碼。

TextEncoder

  • TextEncoder 是用于「將字符串文本編碼為字節(jié)數(shù)組」(通常是 UTF-8 編碼)的對象。
  • 它提供了一個 encode() 方法,接受一個字符串作為參數(shù),并返回一個包含字節(jié)的 Uint8Array 對象。
  • TextEncoder 用于將文本數(shù)據(jù)轉(zhuǎn)換為字節(jié)數(shù)據(jù),以便在網(wǎng)絡(luò)傳輸、文件讀寫或其他需要字節(jié)數(shù)據(jù)的情況下使用。

示例:

const encoder = new TextEncoder();
const text = "前端柒八九!";
const bytes = encoder.encode(text); // 將文本編碼為字節(jié)數(shù)組

TextDecoder

  • TextDecoder 是用于將字節(jié)數(shù)組解碼為字符串文本的對象。
  • 它提供了一個 decode() 方法,接受一個包含字節(jié)的 Uint8Array 對象,并返回相應(yīng)的字符串。
  • TextDecoder 用于將字節(jié)數(shù)據(jù)還原為文本,通常用于處理來自網(wǎng)絡(luò)請求或文件的字節(jié)數(shù)據(jù)。

示例:

const decoder = new TextDecoder("UTF-8");
const bytes = new Uint8Array([
  72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33,
]);
const text = decoder.decode(bytes); // 將字節(jié)數(shù)組解碼為字符串

這些對象在處理「多語言文本」、「字符編碼轉(zhuǎn)換」和處理「國際化內(nèi)容」時非常有用,使 JavaScript 能夠處理不同字符編碼之間的數(shù)據(jù)轉(zhuǎn)換。

Emoji

Emoji 是可以插入文字的圖形符號。

圖片圖片

它是一個日語詞,e表示"絵",moji表示"文字"。連在一起,就是"絵文字"。

2010 年,Unicode 開始為 Emoji 分配碼點(diǎn)。也就是說,「現(xiàn)在的 Emoji 符號就是一個文字」,它會被渲染為圖形。

圖片圖片

想了解更多,可以翻閱Emoji 簡介[2]

2. Unicode 是個啥?

Unicode是一個旨在統(tǒng)一所有人類語言(包括過去和現(xiàn)在的語言)并使它們與計算機(jī)兼容的標(biāo)準(zhǔn)。

Unicode 是一個將「不同字符分配給唯一編號的表格」。

例如:

  • 拉丁字母 A 被分配編號 65。
  • 阿拉伯字母 Seen ?是 1587。
  • 片假名字母 Tu ツ 是 12484
  • 音樂符號 G 調(diào)號 ?? 是 119070。
  • ?? 是 128169。

Unicode 將這些編號稱為「碼位」(code points)。

由于這套準(zhǔn)則是全球都認(rèn)準(zhǔn)的,所以我們采用這套規(guī)則,就可以達(dá)到「書同文」的情況,來自不同語言環(huán)境下的人,可以閱讀彼此的文本。

有如下的關(guān)系鏈子。 一個Unicode對應(yīng)著一個字符,并且該字符擁有幾乎唯一的碼位。

Unicode === 字符 ? 碼位。

Unicode 有多大?

目前,「最大的已定義碼位」是0x10FFFF。(0x10FFFF 是一個十六進(jìn)制數(shù),將其轉(zhuǎn)換為十進(jìn)制,其值為 1,114,111。)這給我們提供了大約 110 萬個碼位的空間。

目前已定義了約 15%(約 170,000 個),另外 11%(為私人使用)已被保留。其余約 800,000 個碼位目前尚未分配,它們可能在未來成為字符。

大致如下圖所示:

圖片圖片

  • 大正方形 包含 65,536 個字符。
  • 小正方形 包含 256 個字符。
  • 整個 ASCII 字符集僅占位于左上角的小紅色正方形的一半。

私人使用區(qū)(Private Use)

私人使用區(qū)是為應(yīng)用程序開發(fā)人員保留的碼位,不會由 Unicode 本身定義。

例如,Unicode 中沒有為蘋果標(biāo)志保留位置,因此蘋果將它放在了 U+F8FF,這位于私人使用區(qū)。在任何其他字體中,它將呈現(xiàn)為缺失的字符 ??,但在與 macOS 一起提供的字體中,我們將看到蘋果圖標(biāo)。

私人使用區(qū)主要用于「圖標(biāo)字體」:

上面的圖標(biāo)都是文本格式

U+1F4A9 是什么意思?

這是一種寫碼位值的約定。前綴 U+表示 Unicode,而 1F4A9 是一個「十六進(jìn)制的碼位編號」。

U+1F4A9 具體表示的是 ??。(是不是我們多了一種很委婉的"表揚(yáng)別人"方式)

3. UTF-8 又是什么?

UTF-8 是一種「編碼方式」。

編碼是我們將碼位存儲在內(nèi)存中的方法。在互聯(lián)網(wǎng)和許多操作系統(tǒng)中,UTF-8是「默認(rèn)的文本編碼」。

最簡單的 Unicode 編碼是 UTF-32。它將碼位簡單地「存儲為 32 位整數(shù)」。因此,U+1F4A9 變成了 00 01 F4 A9,占用了「四個字節(jié)」。UTF-32 中的「任何其他碼位也將占用四個字節(jié)」。由于最高定義的碼位是 U+10FFFF,因此任何碼位都能夠容納。

  • UTF-8通常用于存儲和傳輸文本
  • UTF-16用于某些操作系統(tǒng)和編程語言
  • UTF-16被許多系統(tǒng)采用。其中包括 Microsoft Windows、Objective-C、Java、JavaScript、.NET、Python 2等
  • UTF-32適用于需要直接操作Unicode代碼點(diǎn)的情況

UTF-8 有多少字節(jié)?

UTF-8 是一種「可變長度」的編碼方式。

一個碼位可能被編碼為「一個到四個字節(jié)」的序列。

以下是 UTF-8 編碼的表示形式,「根據(jù)不同的碼位范圍使用不同數(shù)量的字節(jié)」

碼位范圍

Byte 1

Byte 2

Byte 3

Byte 4

U+0000..007F

0xxxxxxx




U+0080..07FF

110xxxxx

10xxxxxx



U+0800..FFFF

1110xxxx

10xxxxxx

10xxxxxx


U+10000..10FFFF

11110xxx

10xxxxxx

10xxxxxx

10xxxxxx

這些規(guī)則描述了如何將不同碼位范圍內(nèi)的 Unicode 字符編碼為 UTF-8 字節(jié)序列。

如果將這些內(nèi)容與 Unicode 表結(jié)合起來,我們將看到

  • 英語使用 1 個字節(jié)進(jìn)行編碼,
  • 西里爾字母、拉丁歐洲語言、希伯來語和阿拉伯語需要 2 個字節(jié),
  • 中文、日語、韓語、其他亞洲語言和表情符號需要 3 或 4 個字節(jié)。

以下是一些重要的要點(diǎn):

首先,UTF-8 與 ASCII 是「字節(jié)兼容」的。碼位 0..127,即舊的 ASCII 字符,使用一個字節(jié)進(jìn)行編碼,而且它們的字節(jié)表示完全相同。例如,U+0041(A,拉丁大寫字母 A)就是 41,一個字節(jié)。

任何純 ASCII 文本也是有效的 UTF-8 文本,而且「只使用碼位 0..127 的 UTF-8 文本可以直接讀取為 ASCII」。

其次,UTF-8 對于基本拉丁字符來說是「空間高效」的。

  • 對于像 HTML 標(biāo)簽或 JSON 這樣的技術(shù)字符串來說,這是有意義的。

第三,UTF-8 內(nèi)置了「錯誤檢測」和「恢復(fù)功能」。

  • 第一個字節(jié)的前綴總是與第 2 到第 4 個字節(jié)不同。這樣,我們始終可以確定是否正在查看完整和有效的 UTF-8 字節(jié)序列,或者是否有遺漏。
  • 然后,我們可以通過向前或向后移動,直到找到正確序列的開頭來進(jìn)行糾正。

還有一些重要的結(jié)論:

  • 我們「無法通過計算字節(jié)來確定字符串的長度」。
  • 我們「無法隨機(jī)跳到字符串的中間并開始閱讀」。
  • 我們無法通過在任意字節(jié)偏移處進(jìn)行「切割來獲取子字符串」,可能會切斷字符的一部分。

如果硬要這么做的話,系統(tǒng)會給你一個?。

“?”是什么?

U+FFFD,即「替換字符」(Replacement Character),只是 Unicode 表中的另一個碼位。應(yīng)用程序和庫可以在檢測到 Unicode 錯誤時使用它。

如果將碼位的一半切掉,那么另一半也就沒什么用了,除了顯示錯誤。這時就會使用?。

JS 版本

const text = "前端柒八九";
const encoder = new TextEncoder();
const bytes = encoder.encode(text);

const partial = bytes.slice(0, 11);
const decoder = new TextDecoder("UTF-8");
const result = decoder.decode(partial);

console.log(result); // 輸出 "前端柒?"

Rust 版本

fn main() {
    let text = "前端柒八九";
    let bytes = text.as_bytes();

    let partial = &bytes[0..11];
    let result = String::from_utf8_lossy(partial);

    println!("{}", result); // 輸出 "前端柒?"
}

在 JavaScript 中使用 TextEncoder 和 TextDecoder 來處理編碼,而在 Rust 中使用 String::from_utf8_lossy 來處理字節(jié)。它們的目標(biāo)是在 UTF-8 編碼中處理文本并「截取部分字節(jié)」。

4. UTF-32 問題

UTF-32 非常適用于處理碼位。它的編碼方式中,「每個碼位始終是 4 個字節(jié)」,那么strlen(s) == sizeof(s) / 4,substring(0, 3) == bytes[0, 12](上面代碼為偽代碼)等等。

問題在于,我們不想處理碼位。一個碼位即「不是一個書寫單位」,又并「不總是代表一個字符」。我們應(yīng)該處理的是擴(kuò)展形素簇(extended grapheme clusters),或簡稱為形素(graphemes)。

形素是在特定書寫系統(tǒng)的上下文中的「最小可區(qū)分」的書寫單位。

例如,? 是一個形素,e?也是一個形素。還有像?這樣的形素。基本上,「形素是用戶認(rèn)為是一個字符的單元」。

問題是,在 Unicode 中,一些形素是由「多個碼位編碼」的!

圖片圖片

例如,e?(一個單一的形素)在 Unicode 中編碼為 e(U+0065 拉丁小寫字母 E)+ ′(U+0301 連接重音符)。兩個碼位!

它也可能不止兩個:

  • ?? 是 U+2639 + U+FE0F
  • ???? 是 U+1F468 + U+200D + U+1F3ED
  • ????♀? 是 U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F
  • y?????????? 是 U+0079 + U+0316 + U+0320 + U+034D + U+0318 + U+0347 + U+0357 + U+030F + U+033D + U+030E + U+035E

即使在最寬的編碼 UTF-32 中,???? 仍需要「三個 4 字節(jié)單元」來進(jìn)行編碼。它仍然需要被「視為一個單獨(dú)的字符」。

我們可以將 Unicode 本身(沒有任何編碼)視為「可變長度」的。

擴(kuò)展形素簇(Extended Grapheme Cluster)是「一個或多個 Unicode 碼位的序列」,必須將其視為「一個單獨(dú)的、不可分割的字符。

因此,在「碼位級別」上:「不能只取序列的一部分,它總是應(yīng)該作為一個整體選擇、復(fù)制、編輯或刪除」。

不正確使用形素簇會導(dǎo)致像這樣的錯誤:

無論是否選擇UTF-32還是UTF-8在處理形素上遇到相似的問題。所以如何使用形素才是我們應(yīng)該關(guān)心的。

5. Unicode 病癥

上面的例子中大部分都是涉及到表情符號,這會給人一種錯覺。Unicode只有在表示表情符號時,會遇到問題。--其實(shí)不是。

擴(kuò)展形素簇也用于常見的語言。

例如:

  • ?(德語)是一個單一字符,但包含多個碼位(U+006F U+0308)。
  • ??(立陶宛語)是 U+00E1 U+0328。
  • ?(韓語)是 U+1100 U+1161 U+11A8。

所以,問題不僅僅是表情符號。

"????♂?".length 是多少?

不同的編程語言給出了不同的結(jié)果。

Python 3:

>>> len("????♂?")
5

JavaScript / Java / C#:

>> "????♂?".length
7

Rust:

println!("{}", "????♂?".len());
// => 17

不同的語言使用不同的「內(nèi)部字符串」表示(UTF-32、UTF-16、UTF-8),并以存儲字符的單位(整數(shù)、短整數(shù)、字節(jié))來報告長度。

但是!如果你問任何不懂編程理論的人,他們會給你一個明確的答案:????♂? 字符串的長度是 1。

這就是擴(kuò)展形素簇的意義:「人們視為單一字符的內(nèi)容」。在這種情況下,????♂? 顯然是一個單一字符。

????♂? 由 5 個碼位組成(U+1F926 U+1F3FB U+200D U+2642 U+FE0F)僅僅是「實(shí)現(xiàn)細(xì)節(jié)」。它不應(yīng)該被分開,「不應(yīng)該被計為多個字符」,文本光標(biāo)不應(yīng)該定位在其中,不應(yīng)該被部分選擇,等等。

這是「文本的一個不可分割的單位」。在內(nèi)部,它可以被編碼為任何形式,但對于面向用戶的 API,應(yīng)該將其視為一個整體。

唯一正確處理此問題的現(xiàn)代語言是 Swift:

print("????♂?".count)
// => 1

而對于我們比較熟悉的JS和Rust,我們可以使用一些方式做一下封裝。

function visibleLength(str) {
  return [...new Intl.Segmenter().segment(str)].length;
}
visibleLength("????♂?"); // 輸出結(jié)果為1

當(dāng)然,我們還可以校驗(yàn)其他的形素。

visibleLength("?"); // => 1
visibleLength("????"); // => 1
visibleLength("????????????"); // => 2
visibleLength("と日本語の文章"); // => 7

但是呢,Intl.Segmenter的兼容性不是很好。

如果,我們要實(shí)現(xiàn)多瀏覽器適配,我們可以找一些第三方的庫。

  • graphemer[3]
  • text-segmentation[4]

如果想了解更多細(xì)節(jié),可以參考JS 如何正確處理 Unicode[5]

對于Rust我們可以使用unicode_segmentation[6]crate。

extern crate unicode_segmentation; // "1.9.0"

use std::collections::HashSet;

use unicode_segmentation::UnicodeSegmentation;

fn count_unique_grapheme_clusters(s: &str) -> usize {
    let is_extended = true;
    s.graphemes(is_extended).collect::<HashSet<_>>().len()
}

fn main() {
    assert_eq!(count_unique_grapheme_clusters(""), 0);
    assert_eq!(count_unique_grapheme_clusters("????♂?"), 1);
    assert_eq!(count_unique_grapheme_clusters("????"), 1);
}

6. 如何檢測擴(kuò)展形素簇

大多數(shù)編程語言選擇了簡單的方式,允許我們迭代字符串時使用 1-2-4 字節(jié)的塊,但「不支持直接處理擴(kuò)展形素簇」。

由于它是默認(rèn)方式,結(jié)果我們看到了損壞的字符串:

圖片圖片

如果遇到這種問題,我們首先的就是應(yīng)該想到使用Unicode 庫。

使用庫

即使是像 strlen、indexOf 或 substring 這樣的基本操作也應(yīng)該使用 Unicode 庫!

例如:

  • C/C++/Java:使用 ICU[7]。這是 Unicode 自身發(fā)布的庫,包含了關(guān)于文本分割的所有規(guī)則。
  • Swift:只需使用標(biāo)準(zhǔn)庫。Swift 默認(rèn)情況下會正確處理。
  • Javascript的話,我們上面提到過,可以使用瀏覽器內(nèi)置功能Intl.Segmenter或者graphemer/text-segmentation
  • Rust而言,我們可以使用unicode_segmentation

不管選擇哪種方式,確保它使用的是「新版本」的 Unicode,因?yàn)樾嗡氐亩x會隨版本而變化。

Unicode 規(guī)則更新

從大約 2014 年開始,Unicode 每年都會發(fā)布其標(biāo)準(zhǔn)的重大修訂版本。

每年更新

圖片圖片

隨之而來的不良反映就是,定義形素簇的規(guī)則每年也會發(fā)生變化。今天被認(rèn)為是由兩個或三個獨(dú)立碼位組成的序列,明天可能會成為一個形素簇!這種朝令夕改的做法,很是讓人深惡痛絕。

更糟糕的是,我們自己的應(yīng)用程序的不同版本可能運(yùn)行在不同的 Unicode 標(biāo)準(zhǔn)上,并報告不同的字符串長度!

7. "A?" !== "?" !== "?"

將其中任何一個復(fù)制到你的 JavaScript 控制臺:

"A?" === "?";
"?" === "?";
"A?" === "?";

你會得到讓你匪夷所思的答案。沒錯,它們的打印結(jié)果都是false。

還記得之前的,? 是由兩個碼位組成,U+006F U+0308 。基本上,Unicode 提供了「多種」編寫字符如 ? 或 ? 的方式。

  1. 通過將普通的拉丁字母 A 與一個組合字符組合成 ?,
  2. 或者使用已經(jīng)預(yù)先組合的碼位 U+00C5。

因?yàn)椋鼈儭缚雌饋硎窍嗤沟模ˋ? 與 ?),所以從用戶的角度,我們就「認(rèn)為它們應(yīng)該是相同」的,但結(jié)果卻和我們的想法大相徑庭。

這就是為什么我們需要規(guī)范化。有四種形式:

這里先從NFD和NFC介紹。

  1. NFD(Normalization Form C) 嘗試將一切都分解為最小可能的部分,并如果存在多個部分,則按照規(guī)范順序?qū)@些部分進(jìn)行排序。

它消除任何規(guī)范化差異,并生成一個「分解的結(jié)果」

  1. NFC(Normalization Form C),嘗試將一切組合成已經(jīng)預(yù)先組合的形式(如果存在)

它消除任何規(guī)范化差異,通常生成一個「合成的結(jié)果」

不同的形式用于不同的用例,以確保文本在不同的方式下都保持一致。所以,盡管"A?" !== "?" !== "?",但通過適當(dāng)?shù)囊?guī)范化,我們可以使它們等同。

圖片圖片

對于某些字符,Unicode 中還存在多個版本。例如,有 U+00C5 帶有上面環(huán)圈的拉丁大寫字母 A,但還有外觀相同的 U+212B ?ngstr?m 符號。

這些字符在規(guī)范化過程中也會被替換,以確保它們的一致性。

圖片圖片

NFD 和 NFC 被稱為“規(guī)范化規(guī)范”(canonical normalization)。另外兩種形式是“兼容規(guī)范化”(compatibility normalization):

  1. NFKD 試圖將「所有內(nèi)容分解」,并使用默認(rèn)形式替換視覺變體。

它消除規(guī)范化和兼容性差異,并生成一個分解的結(jié)果

  1. NFKC 試圖將「所有內(nèi)容組合」在一起,同時用默認(rèn)形式替換視覺變體。

它消除規(guī)范化和兼容性差異,并通常生成一個合成的結(jié)果

圖片圖片

視覺變體是表示相同字符的獨(dú)立 Unicode 碼位,但它們應(yīng)該呈現(xiàn)不同的方式。比如,①、? 或 ??。

圖片圖片

所有這些字符都有自己的碼位,但它們也都是Xs。

在比較字符串或搜索子字符串之前,進(jìn)行規(guī)范化!

`Unicode`規(guī)范化[8]傳送 ??

在JavaScript 中,我們可以使用 normalize() 方法來實(shí)現(xiàn) NFC(Normalization Form C)和 NFD(Normalization Form D)。

const str1 = "A?";
const str2 = "?";

const normalizedStr1 = str1.normalize("NFC"); // NFC 形式
const normalizedStr2 = str2.normalize("NFC"); // NFC 形式

console.log(normalizedStr1 === normalizedStr2); // true

上述代碼首先使用 normalize('NFC') 方法將兩個字符串都轉(zhuǎn)換為 NFC 形式,然后比較它們是否相等。這將使 "A?" 和 "?" 的比較結(jié)果為 true。

如果使用 NFD 形式,只需將 normalize('NFC') 更改為 normalize('NFD') 即可。

8. Unicode 取決于區(qū)域設(shè)置

俄羅斯名字「尼古拉」

圖片圖片

在Unicode 中編碼為 U+041D 0438 043A 043E 043B 0430 0439。

保加利亞名字「尼古拉」

圖片圖片

也寫成 U+041D 0438 043A 043E 043B 0430 0439。

它們的Unicode值完全一樣,但是所顯示的字體信息卻不盡相同。是不是有種小腦萎縮的感覺。

然后心中有一個 ??,計算機(jī)如何知道何時呈現(xiàn)保加利亞風(fēng)格的字形,何時使用俄羅斯的字形?

其實(shí),計算機(jī)也不知。Unicode 并不是一個完美的系統(tǒng),它有很多不足之處。其中一個問題是「將本應(yīng)呈現(xiàn)不同外觀的字形分配給相同的碼位」,比如西里爾字母的小寫字母 K 和保加利亞的小寫字母 K(都是 U+043A)。

針對一些表音語言這塊還能好點(diǎn),但是到了我們大亞洲,很多國家的文字都是「表意」的。許多漢字、日語和韓語表意字形的寫法都截然不同,但被分配了相同的碼位。

圖片圖片

Unicode 的動機(jī)是為了「節(jié)省碼位空間」。渲染信息應(yīng)該在字符串外部以區(qū)域設(shè)置/語言元數(shù)據(jù)的方式傳遞。

在實(shí)踐中,依賴于區(qū)域設(shè)置帶來了許多問題:

  • 作為元數(shù)據(jù),區(qū)域設(shè)置通常會丟失。
  • 人們不限于使用「單一區(qū)域設(shè)置」。例如,我們可以閱讀和寫作中文,美國英語、英國英語、德語和俄語。
  • 難以混合和匹配。比如在保加利亞文本中使用俄羅斯名字,反之亦然。
  • 沒有地方可以指定區(qū)域設(shè)置。即使制作上面的兩個屏幕截圖也不容易,因?yàn)樵诖蠖鄶?shù)軟件中,沒有下拉菜單或文本輸入來更改區(qū)域設(shè)置。

9. 處理特殊語言

另一個不幸的例子是土耳其語中無點(diǎn) i 的 Unicode 處理。

與英語不同,土耳其語有兩種 I 變體:有點(diǎn)和無點(diǎn)。

Unicode 決定重用 ASCII 中的 I 和 i,并只添加了兩個新的碼位:? 和 ?。

這導(dǎo)致了在相同輸入上 toLowerCase/toUpperCase 表現(xiàn)不同:

var en_US = Locale.of("en", "US");
var tr = Locale.of("tr");

System.out.println("I".toLowerCase(en_US)); // => "i"
System.out.println("I".toLowerCase(tr));    // => "?"

System.out.println("i".toUpperCase(en_US)); // => "I"
System.out.println("i".toUpperCase(tr));    // => "?"

所以,我們在不知道字符串是用哪種語言編寫的情況下將字符串轉(zhuǎn)換為小寫,會出現(xiàn)問題。

如果我們項(xiàng)目中涉及到土耳其語的字符轉(zhuǎn)換,在 JS 中toLowerCase是達(dá)不到上面的要求的。因?yàn)椋贘avaScript中,toLowerCase方法默認(rèn)使用Unicode規(guī)范進(jìn)行轉(zhuǎn)換,根據(jù)Unicode的規(guī)范,大寫 I 被轉(zhuǎn)換為小寫 i,而不是 ?。這是因?yàn)镴avaScript的toLowerCase方法按照Unicode的標(biāo)準(zhǔn)工作。

要想使用JS正確處理上面的問題,我們就需要額外的 API.

"I".toLocaleLowerCase("tr-TR"); // => "?"
"i".toLocaleUpperCase("tr-TR"); // => "?"

我們也可以通過對String.prototype上做一層封裝。

String.prototype.turkishToUpper = function () {
  var string = this;
  var letters = { i: "?", ?: "?", ?: "?", ü: "ü", ?: "?", ?: "?", ?: "I" };
  string = string.replace(/(([i???ü??]))+/g, function (letter) {
    return letters[letter];
  });
  return string.toUpperCase();
};

String.prototype.turkishToLower = function () {
  var string = this;
  var letters = { ?: "i", I: "?", ?: "?", ?: "?", ü: "ü", ?: "?", ?: "?" };
  string = string.replace(/(([?I??ü??]))+/g, function (letter) {
    return letters[letter];
  });
  return string.toLowerCase();
};

// 代碼演示
"D?N?".turkishToLower(); // => din?
"DIN?".turkishToLower(); // => d?n?

這樣就可以正確規(guī)避JS針對土耳其語言中的準(zhǔn)換問題。

在Rust中,我們可以使用如下代碼:

fn turkish_to_upper(input: &str) -> String {
    let letters = [
        ('i', "?"),
        ('?', "?"),
        ('?', "?"),
        ('ü', "ü"),
        ('?', "?"),
        ('?', "?"),
        ('?', "I"),
    ];

    let mut result = String::new();

    for c in input.chars() {
        let mut found = false;
        for &(source, target) in &letters {
            if c == source {
                result.push_str(target);
                found = true;
                break;
            }
        }
        if !found {
            result.push(c);
        }
    }

    result.to_uppercase()
}

fn turkish_to_lower(input: &str) -> String {
    let letters = [
        ('?', "i"),
        ('I', "?"),
        ('?', "?"),
        ('?', "?"),
        ('ü', "ü"),
        ('?', "?"),
        ('?', "?"),
    ];

    let mut result = String::new();

    for c in input.chars() {
        let mut found = false;
        for &(source, target) in &letters {
            if c == source {
                result.push_str(target);
                found = true;
                break;
            }
        }
        if !found {
            result.push(c);
        }
    }

    result.to_lowercase()
}

fn main() {
    let input = "???ü???";

    let upper_result = turkish_to_upper(input);
    let lower_result = turkish_to_lower(input);

    println!("Upper: {}", upper_result); //Upper: ???ü??I
    println!("Lower: {}", lower_result); // Lower: i??ü???
}

Reference

[1]ASCII:https://cikgucandoit.wordpress.com/what-is-ascll/

[2]Emoji 簡介:https://www.ruanyifeng.com/blog/2017/04/emoji.html

[3]graphemer:https://github.com/flmnt/graphemer

[4]text-segmentation:https://github.com/niklasvh/text-segmentation

[5]JS 如何正確處理 Unicode:https://flaviocopes.com/javascript-unicode/

[6]unicode_segmentation:https://docs.rs/unicode-segmentation/latest/unicode_segmentation/

[7]ICU:https://github.com/unicode-org/icu

[8]Unicode規(guī)范化:https://www.unicode.org/glossary/

責(zé)任編輯:武曉燕 來源: 前端柒八九
相關(guān)推薦

2019-10-21 09:40:17

JavaScript瀏覽器Flash

2023-11-07 08:35:26

2020-08-17 07:59:47

IoC DINestJS

2020-06-12 10:00:25

前端tsconfig.js命令

2024-03-26 11:52:13

2020-07-08 14:50:18

WebpackHMR前端

2011-12-28 21:23:14

Windows Pho

2023-02-10 08:22:43

Unicode統(tǒng)一碼萬國碼

2010-07-12 13:39:38

SQL Server

2021-05-13 10:48:49

人工智能農(nóng)業(yè)技術(shù)

2010-07-23 14:53:21

Perl Unicod

2014-12-12 10:13:12

JavaScript

2020-09-21 08:56:00

GolangUnicode編碼

2018-08-12 08:30:10

女性網(wǎng)絡(luò)安全專家網(wǎng)絡(luò)安全

2018-07-05 10:56:42

白熊視頻 京東618

2023-01-28 10:55:39

Unicode代碼

2021-08-02 18:16:41

比特幣以太坊貨幣

2023-04-26 14:15:42

2010-03-24 11:37:22

Python unic

2011-08-15 14:46:46

unicode_sta中文man
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 午夜视频免费在线观看 | 精品国产aⅴ | 亚洲精品黄色 | 国产欧美一区二区三区免费 | 龙珠z在线观看 | www精品美女久久久tv | 日韩欧美在| 91中文字幕在线观看 | 国产yw851.c免费观看网站 | 黄色网址在线播放 | 婷婷91| 中文字幕国产精品 | 欧美一区二区三区在线观看视频 | 国产精品一二区 | av天空| 91欧美精品成人综合在线观看 | 一a级片 | 一区二区三区视频免费观看 | 中文字幕在线不卡播放 | 亚洲视频免费在线观看 | 91精品在线看 | 一区二区国产精品 | 久久av一区| 国产午夜精品一区二区三区 | 中文字幕 国产 | 一区二区视频在线 | 毛片免费观看 | 国产一区二区三区精品久久久 | 久久综合伊人一区二区三 | 久久精品综合网 | 亚洲啪啪| 综合网视频| 一区二区三区视频在线观看 | 一级毛片免费 | 国产欧美一区二区三区日本久久久 | 欧美色综合一区二区三区 | 亚洲国产成人精品女人 | 国产精品视频一区二区三区不卡 | 欧美综合一区 | 欧美精品一区二区免费 | 视频一区二区在线 |