精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

了不起的Unicode

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

前言

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

"????♂?"這個(gè)Emoji的長(zhǎng)度是多少?

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

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

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

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

"A?" === "?";

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

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

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

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

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

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

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

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

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

ASCll

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

圖片圖片

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

ASCII 的功能

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

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

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

TextEncoder 和 TextDecoder

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

TextEncoder

  • TextEncoder 是用于「將字符串文本編碼為字節(jié)數(shù)組」(通常是 UTF-8 編碼)的對(duì)象。
  • 它提供了一個(gè) encode() 方法,接受一個(gè)字符串作為參數(shù),并返回一個(gè)包含字節(jié)的 Uint8Array 對(duì)象。
  • 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ù)組解碼為字符串文本的對(duì)象。
  • 它提供了一個(gè) decode() 方法,接受一個(gè)包含字節(jié)的 Uint8Array 對(duì)象,并返回相應(yīng)的字符串。
  • TextDecoder 用于將字節(jié)數(shù)據(jù)還原為文本,通常用于處理來(lái)自網(wǎng)絡(luò)請(qǐng)求或文件的字節(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ù)組解碼為字符串

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

Emoji

Emoji 是可以插入文字的圖形符號(hào)。

圖片圖片

它是一個(gè)日語(yǔ)詞,e表示"絵",moji表示"文字"。連在一起,就是"絵文字"。

2010 年,Unicode 開(kāi)始為 Emoji 分配碼點(diǎn)。也就是說(shuō),「現(xiàn)在的 Emoji 符號(hào)就是一個(gè)文字」,它會(huì)被渲染為圖形。

圖片圖片

想了解更多,可以翻閱Emoji 簡(jiǎn)介[2]

2. Unicode 是個(gè)啥?

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

Unicode 是一個(gè)將「不同字符分配給唯一編號(hào)的表格」。

例如:

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

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

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

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

Unicode === 字符 ? 碼位。

Unicode 有多大?

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

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

大致如下圖所示:

圖片圖片

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

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

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

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

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

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

U+1F4A9 是什么意思?

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

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

3. UTF-8 又是什么?

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

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

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

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

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

UTF-8 是一種「可變長(zhǎng)度」的編碼方式。

一個(gè)碼位可能被編碼為「一個(gè)到四個(gè)字節(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é)合起來(lái),我們將看到

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

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

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

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

其次,UTF-8 對(duì)于基本拉丁字符來(lái)說(shuō)是「空間高效」的。

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

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

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

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

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

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

“?”是什么?

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

如果將碼位的一半切掉,那么另一半也就沒(méi)什么用了,除了顯示錯(cuò)誤。這時(shí)就會(huì)使用?。

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 來(lái)處理編碼,而在 Rust 中使用 String::from_utf8_lossy 來(lái)處理字節(jié)。它們的目標(biāo)是在 UTF-8 編碼中處理文本并「截取部分字節(jié)」。

4. UTF-32 問(wèn)題

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

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

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

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

問(wèn)題是,在 Unicode 中,一些形素是由「多個(gè)碼位編碼」的!

圖片圖片

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

它也可能不止兩個(gè):

  • ?? 是 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 中,???? 仍需要「三個(gè) 4 字節(jié)單元」來(lái)進(jìn)行編碼。它仍然需要被「視為一個(gè)單獨(dú)的字符」。

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

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

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

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

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

5. Unicode 病癥

上面的例子中大部分都是涉及到表情符號(hào),這會(huì)給人一種錯(cuò)覺(jué)。Unicode只有在表示表情符號(hào)時(shí),會(huì)遇到問(wèn)題。--其實(shí)不是。

擴(kuò)展形素簇也用于常見(jiàn)的語(yǔ)言。

例如:

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

所以,問(wèn)題不僅僅是表情符號(hào)。

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

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

Python 3:

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

JavaScript / Java / C#:

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

Rust:

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

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

但是!如果你問(wèn)任何不懂編程理論的人,他們會(huì)給你一個(gè)明確的答案:????♂? 字符串的長(zhǎng)度是 1。

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

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

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

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

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

而對(duì)于我們比較熟悉的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("と日本語(yǔ)の文章"); // => 7

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

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

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

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

對(duì)于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. 如何檢測(cè)擴(kuò)展形素簇

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

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

圖片圖片

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

使用庫(kù)

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

例如:

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

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

Unicode 規(guī)則更新

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

每年更新

圖片圖片

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

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

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

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

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

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

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

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

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

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

這里先從NFD和NFC介紹。

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

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

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

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

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

圖片圖片

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

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

圖片圖片

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

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

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

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

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

圖片圖片

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

圖片圖片

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

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

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

在JavaScript 中,我們可以使用 normalize() 方法來(lái)實(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') 方法將兩個(gè)字符串都轉(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值完全一樣,但是所顯示的字體信息卻不盡相同。是不是有種小腦萎縮的感覺(jué)。

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

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

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

圖片圖片

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

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

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

9. 處理特殊語(yǔ)言

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

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

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

這導(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));    // => "?"

所以,我們?cè)诓恢雷址怯媚姆N語(yǔ)言編寫的情況下將字符串轉(zhuǎn)換為小寫,會(huì)出現(xiàn)問(wèn)題。

如果我們項(xiàng)目中涉及到土耳其語(yǔ)的字符轉(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正確處理上面的問(wèn)題,我們就需要額外的 API.

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

我們也可以通過(guò)對(duì)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針對(duì)土耳其語(yǔ)言中的準(zhǔn)換問(wè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 簡(jiǎn)介: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é)任編輯:武曉燕 來(lái)源: 前端柒八九
相關(guān)推薦

2019-10-21 09:40:17

JavaScript瀏覽器Flash

2020-08-17 07:59:47

IoC DINestJS

2023-11-07 08:35:26

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

2010-07-12 13:39:38

SQL Server

2023-02-10 08:22:43

Unicode統(tǒng)一碼萬(wàn)國(guó)碼

2014-12-12 10:13:12

JavaScript

2010-07-23 14:53:21

Perl Unicod

2021-05-13 10:48:49

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

2020-09-21 08:56:00

GolangUnicode編碼

2018-07-05 10:56:42

白熊視頻 京東618

2018-08-12 08:30:10

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

2023-01-28 10:55:39

Unicode代碼

2010-03-24 11:37:22

Python unic

2011-08-15 14:46:46

unicode_sta中文man

2021-08-02 18:16:41

比特幣以太坊貨幣

2023-04-26 14:15:42

點(diǎn)贊
收藏

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

少妇喷水在线观看| 国产一国产二国产三| 欧美一级在线| 亚洲精品成人天堂一二三| 高清av免费一区中文字幕| aaa人片在线| 欧美性videos| 国产激情一区二区三区| 日本国产一区二区三区| 99久久99久久精品国产| 日韩欧美天堂| 777午夜精品免费视频| 97超碰在线人人| 日本高清中文字幕在线| 99精品国产一区二区三区不卡| 国产成人精品一区二区三区| 久草中文在线视频| 日韩成人精品一区| 日本道在线观看一区二区| 97超碰人人爱| 1024免费在线视频| av动漫一区二区| 91在线观看欧美日韩| 美国一级黄色录像| 国模一区二区| 亚洲福利电影网| 椎名由奈jux491在线播放| 天天爽夜夜爽夜夜爽| 国内精品自线一区二区三区视频| 日韩亚洲在线观看| 国产精品815.cc红桃| 亚洲性视频在线| 91麻豆精品国产无毒不卡在线观看| 国产免费一区二区三区视频| 男人天堂综合| 丝袜诱惑制服诱惑色一区在线观看| 亚洲精品一区二区网址| 日韩精品――色哟哟| 欧美午夜大胆人体| 成人av网站在线观看免费| 国产日韩视频在线观看| 69成人免费视频| 亚洲美女色禁图| 欧美大码xxxx| 91嫩草|国产丨精品入口| 成人写真视频| 中文字幕免费国产精品| 人妻体内射精一区二区| 天堂俺去俺来也www久久婷婷 | h狠狠躁死你h高h| 麻豆国产精品一区二区三区 | 成人区精品一区二区| 91女人18毛片水多国产| 欧美成人tv| 亚洲欧美综合区自拍另类| 性色av蜜臀av浪潮av老女人| 成人直播在线观看| 欧美巨大另类极品videosbest| 中文字幕中文字幕在线中心一区| 1769在线观看| 最新成人av在线| 久久最新免费视频| 在线免费观看污| 一区二区三区在线观看动漫| 亚洲精品国产suv一区88| 欧美videossex| 亚洲高清免费视频| 女性女同性aⅴ免费观女性恋 | 亚洲成人动漫在线观看| 国产九九九九九| 中文字幕色婷婷在线视频| 色综合久久久久网| 538任你躁在线精品免费| 日本电影久久久| 日韩欧美另类在线| 五十路六十路七十路熟婆| 亚洲v天堂v手机在线| 亚洲一级片在线看| 国产色无码精品视频国产| 午夜视频一区| 91精品国产色综合久久不卡98口| 最新中文字幕一区| 日韩和欧美一区二区| 国产自产女人91一区在线观看| 99久久一区二区| 成a人片国产精品| 日韩欧美三级电影| 国产剧情在线| 高跟丝袜一区二区三区| 国产成人免费高清视频| 999福利在线视频| 色综合亚洲欧洲| 午夜精品久久久久久久99热影院| 91精品啪在线观看国产爱臀| 亚洲精品第一国产综合精品| 欧美一级片在线免费观看| 全球av集中精品导航福利| 中文字幕亚洲精品| 国产一级片视频| 日本欧美在线观看| 国产精品xxxx| 在线免费观看黄| 午夜精品久久久久影视| 91蝌蚪视频在线观看| 51精品国产| 少妇久久久久久| 国产精品老女人| 国产一区二区精品在线观看| 欧美国产综合视频| 91网在线看| 欧美日韩的一区二区| 最近中文字幕无免费| 久久久精品久久久久久96| 18性欧美xxxⅹ性满足| 国产男男gay体育生白袜| 久久综合色综合88| 91黄色在线看| 亚洲我射av| 国产一区二区三区在线看| 国产一级视频在线| 狠狠色综合播放一区二区| 欧美日韩国产免费一区二区三区| 一色桃子av在线| 欧美日韩国产一区二区三区地区| av网站免费在线播放| 影音先锋在线一区| 亚洲一区久久久| 精品黄色免费中文电影在线播放| 欧美午夜精品久久久久久浪潮 | 亚洲视频重口味| 视频精品一区二区| 久久久久国产精品视频| 爱情岛亚洲播放路线| 欧美一级午夜免费电影| 91视频最新网址| 国产一区激情| 92国产精品视频| 美女隐私在线观看| 欧美日韩成人激情| 1024在线看片| 蜜臀久久99精品久久久久宅男 | 久久久蜜臀国产一区二区| 欧美三级华人主播| 性感女国产在线| 亚洲精品成a人在线观看| 久久久久久久久久久久国产| 国产精品亚洲第一区在线暖暖韩国| 亚洲一区3d动漫同人无遮挡 | 91精品久久久久久久久久久久| 国产亚洲欧洲| 麻豆精品传媒视频| 欧美美女日韩| 中文字幕成人精品久久不卡| 国产成人精品亚洲| 国产精品护士白丝一区av| 青青草精品视频在线观看| 欧美极品中文字幕| 国产精品黄视频| 一级毛片视频在线| 4438x成人网最大色成网站| 亚洲人与黑人屁股眼交| 久久国产精品99久久人人澡| 高清一区二区三区视频| 丰满诱人av在线播放| 亚洲第一视频网| 亚洲va在线观看| 日本一区二区免费在线观看视频 | 成人97人人超碰人人99| 人妻少妇精品无码专区二区| 99久久亚洲国产日韩美女| 中文字幕欧美国内| 国产乱色精品成人免费视频| 玉米视频成人免费看| 日韩综合第一页| 久久久国产亚洲精品| 亚洲啪啪av| 亚洲一区二区三区免费| 55夜色66夜色国产精品视频| 国产福利第一视频在线播放| 欧美日韩精品是欧美日韩精品| 中文字幕另类日韩欧美亚洲嫩草| 成人深夜福利app| 免费观看成人在线视频| 一区二区电影| 九九热久久66| 日本久久二区| 97在线视频国产| 午夜视频成人| 亚洲国产精品成人精品| 最近中文字幕在线观看视频| 亚洲人午夜精品天堂一二香蕉| 人妻av一区二区| 精品中文字幕一区二区小辣椒| 成年女人18级毛片毛片免费| 国产精品欧美日韩一区| 91在线免费看片| 忘忧草在线日韩www影院| 爱福利视频一区| 午夜福利视频一区二区| 6080国产精品一区二区| www.中文字幕在线观看| 亚洲欧美aⅴ...| 瑟瑟视频在线观看| 国产91丝袜在线播放0| 久久精品免费网站| 综合久久亚洲| 婷婷精品国产一区二区三区日韩| 999久久久精品一区二区| 国产精品久久久久久久9999| caoporn视频在线观看| 日韩在线观看免费高清完整版| 特黄视频在线观看| 欧美一区二区日韩| 99re热视频| 精品色蜜蜜精品视频在线观看| 91精品一区二区三区蜜桃| 久久精品一区四区| 好吊操视频这里只有精品| 久久精品国产精品亚洲精品| 97在线播放视频| 亚洲激情综合| 国产精品免费看久久久无码| 欧美激情电影| 深夜福利成人| 国内成人精品| 青青草久久网络| 日本三级久久| 久久国产精品精品国产色婷婷| 奇米一区二区| 91在线播放视频| 久久综合给合| 91久久久精品| 国产电影一区二区| 国产精品中文字幕久久久| 美女写真久久影院| 日韩av免费一区| 91大神xh98hx在线播放| 亚洲欧洲日产国产网站| 艳母动漫在线看| 亚洲精品97久久| 色噜噜在线播放| 亚洲精品福利在线观看| 日本毛片在线观看| 亚洲福利视频专区| 色窝窝无码一区二区三区| 精品日韩在线观看| 丰满岳乱妇国产精品一区| 精品久久香蕉国产线看观看亚洲| 国产一级特黄视频| 亚洲va韩国va欧美va| 国产精品a成v人在线播放| 亚洲va国产va欧美va观看| 日韩xxx高潮hd| 欧美日韩精品在线| 亚洲欧美综合另类| 欧美中文字幕亚洲一区二区va在线 | 99视频在线观看免费| 91精品国产福利| 一区二区国产欧美| 欧美浪妇xxxx高跟鞋交| 97在线播放免费观看| 欧美一级二级三级蜜桃| 日本黄视频在线观看| 日韩精品有码在线观看| 国产爆初菊在线观看免费视频网站 | 欧美午夜免费影院| 97在线免费视频观看| 亚洲久色影视| 无码日韩人妻精品久久蜜桃| 久久国产婷婷国产香蕉| 深夜福利网站在线观看| 成人黄色a**站在线观看| 国产在线观看无码免费视频| 国产女人水真多18毛片18精品视频| 国产wwwwxxxx| 亚洲一区二区五区| 日韩在线 中文字幕| 欧美日韩国产综合视频在线观看| 性生活免费网站| 欧美美女一区二区| 超碰在线人人干| 日韩电视剧在线观看免费网站| 1024视频在线| 国产一级精品在线| 5566日本婷婷色中文字幕97| 影视一区二区三区| 国产在线精品成人一区二区三区| 国产精品视频一区二区三区综合| 国产精品一区视频网站| 青青草国产一区二区三区| 99热在线播放| 国产一区二区三区四区五区传媒 | 久久久久久久久国产一区| 日韩成人三级视频| 老司机免费视频久久| 国产xxxxhd| 国产亚洲欧美中文| 久久久精品视频在线| 欧洲精品中文字幕| 欧性猛交ⅹxxx乱大交| 色妞久久福利网| 少妇在线看www| 久久久久久久激情视频| 国产a亚洲精品| 日韩在线观看免费高清| 国产精品专区免费| 国产a一区二区| 99热国内精品永久免费观看| 国产精品丝袜久久久久久消防器材| 激情文学综合插| 中文字幕人妻一区二区三区在线视频| 亚洲男同性视频| 中文文字幕一区二区三三| 亚洲精品国产精品乱码不99按摩| 国产三区在线观看| 国产精品成熟老女人| 欧美偷窥清纯综合图区| 欧美 日韩 国产 在线观看| 国产精品国内免费一区二区三区| 国产一区二区网| 国产丶欧美丶日本不卡视频| av片在线免费看| 色香色香欲天天天影视综合网| 亚洲精品一区二区三区区别| 日韩在线欧美在线国产在线| 欧美男女交配| 欧美精品在线一区| 一本色道88久久加勒比精品| 永久看看免费大片| 1区2区3区国产精品| 一区二区视频网站| 一区二区欧美激情| 韩国久久久久久| 欧美精品亚洲精品| 国产一区二区三区的电影| 亚洲图片欧美另类| 亚洲尤物视频在线| 超碰在线观看99| 欧美日韩福利电影| 自拍偷自拍亚洲精品被多人伦好爽| 精品欧美国产| 中文国产一区| 99久久国产精| 日韩欧美中文字幕在线观看| 无码国产伦一区二区三区视频| 国产+成+人+亚洲欧洲| 老司机在线精品视频| 日本欧美黄色片| 91网站在线观看视频| 91午夜视频在线观看| 精品视频一区在线视频| 成人直播视频| 神马影院我不卡| 狠狠狠色丁香婷婷综合激情| 综合五月激情网| 精品日本一线二线三线不卡| av午夜在线观看| 精品国产日本| 日韩在线播放一区二区| 免费黄色在线网址| 91精品在线一区二区| 性欧美video高清bbw| 国产偷国产偷亚洲高清97cao| 国产一区二区精品| 天天躁日日躁aaaa视频| 欧美日韩精品一区二区三区蜜桃 | 国模吧精品视频| 手机av在线免费| 亚洲综合色丁香婷婷六月图片| 搡老岳熟女国产熟妇| 国产精品aaa| 68国产成人综合久久精品| 26uuu国产| 日韩欧美国产视频| 在线播放麻豆| 成人免费在线一区二区三区| 亚洲综合国产激情另类一区| 免费成人深夜天涯网站| 日韩欧美国产电影| 黑人巨大亚洲一区二区久| 在线观看亚洲视频啊啊啊啊| 成人午夜激情在线| 夜夜爽妓女8888视频免费观看| 久久精品99久久久久久久久| 成人涩涩网站| 黄色免费网址大全| 亚洲综合激情网| 九一国产在线| 亚洲自拍高清视频网站| 小嫩嫩精品导航| 欧美老熟妇一区二区三区| 日韩精品视频免费| 色猫猫成人app| 日本中文字幕在线视频观看| 中文字幕不卡在线| 日韩一区免费视频| 成人国产精品一区二区| 午夜亚洲影视| 欧美精品久久久久性色| 一区二区三区视频免费|