成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

詳解 ES6 Unicode

PiscesYE / 2645人閱讀

摘要:我們得從原因理解起。這個(gè)編碼位置是唯一的。為了確保其組織性,把這個(gè)範(fàn)圍的編碼區(qū)分成個(gè)區(qū)段,各自由個(gè)編碼組成。由進(jìn)制格式的個(gè)位元組成代表一個(gè)編碼位置。跳脫序列可以被用來(lái)表示編碼位置從到。

為了理解 ES6 到底對(duì)於 Unicode 萬(wàn)國(guó)碼有哪些新的支援。我們得從原因理解起。

Javascript 在處理 Unicode 時(shí)很有多問(wèn)題

關(guān)於 Javascript 處理 Unicode 的方式...至少可以說(shuō)是很奇怪。這篇文章闡述在 Javascript 中存取 Unicode 的痛點(diǎn)以及 ES6 如何改善這個(gè)問(wèn)題。

Unicode 基礎(chǔ)

在我們深入探討 Javascript 之前,讓我們先確認(rèn)當(dāng)我們談到 Unicode 的時(shí)候說(shuō)的是相同的事情。

有關(guān) Unicode 的觀念其實(shí)非常簡(jiǎn)單,把它想成一個(gè)資料庫(kù),存取著您能想到的所有文字符號(hào),且每一個(gè)文字符號(hào)都對(duì)應(yīng)著一組數(shù)字。這個(gè)數(shù)字就叫編碼位置(Code point),也有人稱碼點(diǎn) 代碼點(diǎn)。這個(gè)編碼位置是唯一的。透過(guò)這種方式可以簡(jiǎn)單的存取特定文字符號(hào)而不用直接輸入符號(hào)本身。

例如:

A = U+0041

a = U+0061

? = U+00A9

? = U+2603

? = U+1F4A9

編碼位置通常使用 16 進(jìn)制的格式,位元左邊捕 0 到至少 4 位,使用 U+ 當(dāng)作前綴字。
編碼可能的範(fàn)圍從 U+0000U+10FFFF 超過(guò) 110 萬(wàn)個(gè)符號(hào)。為了確保其組織性,Unicode 把這個(gè)範(fàn)圍的編碼區(qū)分成 17 個(gè)區(qū)段,各自由 65536 個(gè)編碼組成。
如果你曾經(jīng)看過(guò) Wiki 百科上的翻譯,他翻成平面,由 17 個(gè)平面組成。

第一個(gè)平面稱作基本多文種平面 Basic Multilingual Plane, 簡(jiǎn)稱BMP。這大概是最重要的一個(gè)。它包含了大部份常用的字符。一般使用英文的情況下您不會(huì)需要 BMP 以外的編碼來(lái)編輯文件。

BMP 以外剩下大概 1 百萬(wàn)個(gè)符號(hào)屬於補(bǔ)充平面(Supplementary planes or Astral planes)
補(bǔ)充平面的字非常好辨別: 如果某個(gè)字符需要超過(guò) 4 位元的 16 進(jìn)制來(lái)表示那它就屬於補(bǔ)充平面。

現(xiàn)在我們有了對(duì) Unicode 的基本認(rèn)識(shí)了。來(lái)看看如何應(yīng)用到 Javascript 的字串。

跳脫序列(Escape sequence)
console.log("x41x42x43");
// "ABC"

console.log("x61x62x63");
// "abc"

這個(gè)東西術(shù)語(yǔ)叫做 16 進(jìn)制的跳脫序列(字元)。由 16 進(jìn)制格式的 2 個(gè)位元組成代表一個(gè)編碼位置。舉例來(lái)說(shuō) x41 代表 U+0041。
跳脫序列可以被用來(lái)表示編碼位置從 U+0000U+00FF。

另外一種常見(jiàn)的跳脫序列的表示類型如下

console.log("u0041u0042u0043");
// "ABC"

console.log("I u2661 JavaScript");
// "I ? JavaScript"

這種格式被稱作萬(wàn)國(guó)碼跳脫序列,算了!還是記英文吧!Unicode escape squences 由16 進(jìn)制格式 4 個(gè)位元組成精準(zhǔn)的表達(dá)編碼位置,舉例來(lái)說(shuō): u2661 表示 U+2661 這種跳脫序列可以用來(lái)表示 U+0000U+FFFF 範(fàn)圍的萬(wàn)國(guó)碼 Unicode 等於是整個(gè)基本多文種平面(BMP)

那麼..其他平面呢? 我們需要大於 4 位元來(lái)表示其他編碼位置啊! 我們要如何使用跳脫序列呈現(xiàn)它們?

ES6 引進(jìn)了新類型的跳脫序列: Unicode code point escapes 讓事情變得比較簡(jiǎn)單

舉例來(lái)說(shuō):

console.log("u{41}u{42}u{43}");
// "ABC"

console.log("u{1F4A9}");
// "?" U+1F4A9

在大括號(hào)之間您可以使用 6 位元的 16 進(jìn)制,這麼一來(lái)就足夠表示所有的 Unicode 編碼。
所以透過(guò)這種類型的跳脫序列您可以輕易的輸出任何您想用的符號(hào)

為了兼容 ES5 和舊有的環(huán)境,一個(gè)不是很好的解決方案出現(xiàn)了,就是使用成對(duì)編碼來(lái)代理

console.log("uD83DuDCA9");
// "?" U+1F4A9

在這種情況下每一個(gè)跳脫字元(跳脫序列)代表一半的編碼位置,2 個(gè)代理編碼組成一個(gè)字符的 Code point。

注意到這個(gè)編碼沒(méi)辦法很直覺(jué)的看出其規(guī)則,這是有一套公式的

例如一個(gè) C 字符大於 0xFFFF 就得對(duì)應(yīng)到 成對(duì)的代理編碼

H = Math.floor((C - 0x10000) / 0x400) + 0xD800
L = (C - 0x10000) % 0x400 + 0xDC00

之後我們提到代理編碼指的就是兩個(gè)編碼其中之一 第一個(gè)的是 H, 第二個(gè)是 L

要反轉(zhuǎn)回來(lái)則是

C = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000

透過(guò)這種代理編碼的機(jī)制所有補(bǔ)充平面的編碼位置(U+010000 - U+10FFFF) 都可以使用。不過(guò)使用單一跳脫字元來(lái)表示 BMP 裡面的字,兩個(gè)跳脫字元(代理編碼)來(lái)處理剩下補(bǔ)充平面的字很容易讓人搞混,造成很多惱人的後果。

計(jì)算 JavaScript 字串的文字(符號(hào))

假設(shè)您想計(jì)算一個(gè)字串的文字有幾個(gè),您會(huì)怎麼處理呢?

直覺(jué)的想法大概是使用 length

console.log("A".length);
// 1

console.log("A" == "u0041");
// true

上面這個(gè)例子 length 剛好是字元的數(shù)量,說(shuō)有 1 個(gè)文字這很合理。
很顯然的我們每一個(gè)文字只需要一個(gè)跳脫字元,但實(shí)際上卻不是這樣。例如:

console.log("?".length); // U+1D400 注意這不只是全形A
// 2 

console.log("?" == "uD835uDC00");
// true

console.log("?".length) // U+1D401 
// 2

console.log("?" == "uD835uDC01");
// true

console.log("?".length);
// 2

console.log("?" == "uD83DuDCA9");
// true

在內(nèi)部 JavaScript 把補(bǔ)充平面的字符視為兩個(gè)跳脫字元(代理編碼)表示一個(gè)字。如果您在 ES5 兼容的瀏覽器輸出您會(huì)看到他把他視為兩個(gè)跳脫字元 length 為 2 ,人們對(duì)於字面上只顯示一個(gè)字但是 length 卻為 2 會(huì)產(chǎn)生困惑。

計(jì)算補(bǔ)充平面裡的文字

回到剛剛的問(wèn)題,那我們?nèi)绾斡?jì)算 JS 字串中有幾個(gè)字?
這個(gè)小技巧針對(duì)代理編碼做處理,當(dāng)我們認(rèn)出這兩個(gè)跳脫字元會(huì)組成一個(gè)字的時(shí)候只計(jì)算一次

var regexAstralSymbols = /[uD800-uD8FF][uDC00-uDCFF]/g;

function countSymbols(string) {
  return string.replace(regexAstralSymbols, "_").length;
}

或者您也可以使用 Punycode.js,punycode.ucs2.decode 方法可以取得一個(gè)字串並回傳一個(gè)包含 Unicode 編碼位置的陣列。如此一來(lái)您就可以計(jì)算幾個(gè)字了。

在 ES6 您可以透過(guò) Array.from 做類似的事情,透過(guò)使用字串的 iterator 來(lái)切割字串成為一個(gè)陣列

var astral = Array.from("???");
console.log(astral);
console.log(astral.length);
// 3

或者使用 ...

console.log([..."???"].length)
// 3

使用上面提到的這些方法,我們可以解決計(jì)算幾個(gè)字的問(wèn)題。

看起來(lái)一樣,但卻不一樣

但是如果我們開始去賣弄我們從文章中學(xué)到的知識(shí),計(jì)算文字的數(shù)量甚至更多複雜的操作例如下面這段程式碼

console.log("ma?ana" == "ma?ana");
// false

JavaScript 會(huì)告訴我們這兩個(gè)字串不一樣,但看起來(lái)明明就一樣。
試著到這個(gè)網(wǎng)址看看

Javascript escapes 工具告訴我們其中的不同

console.log("maxF1ana" == "manu0303ana");
// false

console.log("maxF1ana".length);
// 6

console.log("manu0303ana".length);
// 7

第一個(gè)字串包含的是 U+00F1 是一個(gè)拉丁字小寫 N 加上波浪號(hào)。而第二個(gè)字串裡面的是 U+006E 拉丁字小寫 N 加上 U+0303 波浪號(hào),兩個(gè)編碼合體成一個(gè)字。這樣你明白了為什麼他們不一樣了吧。

然而如果我們希望兩個(gè)字串計(jì)算結(jié)果都會(huì)是 6 個(gè)字呢?
在 ES6 也相當(dāng)直覺(jué)

var normalized = "ma?ana".normalize("NFC"); // 把字串標(biāo)準(zhǔn)化

console.log(Array.from(normalized).length);
// 6
console.log([...normalized].length);
// 6

這個(gè)標(biāo)準(zhǔn)化 normalize 方法是內(nèi)建 String.prototype 的方法,他會(huì)根據(jù)Unicode normalization的規(guī)則執(zhí)行,找出那些字的差異,如果找到那種由兩個(gè)代理編碼組成的字卻長(zhǎng)得跟另一單一編碼位置一樣的字,它會(huì)把它轉(zhuǎn)成單一的那種編碼。

[..."ma?ana"].lenght // U+00F1
// 6
[..."ma?ana"].length // U+006E + U+0303
// 6

// 透過(guò)程式碼驗(yàn)證
var normalized = "ma?ana".normalize("NFC");
console.log(normalized[2] + " = " + normalized.charCodeAt(2))
// ? = 241, 241 轉(zhuǎn)成 16 進(jìn)制 F1

為了向下相容 ES5 和舊環(huán)境可以使用這個(gè)Polyfill

事情還很複雜 - 計(jì)算其他組合式的代理編碼

光上面這些還不夠完美,編碼位置可以有多種組合方式其結(jié)果看起來(lái)是一個(gè)字,但是卻沒(méi)有標(biāo)準(zhǔn)化的格式(或者說(shuō)沒(méi)有相同樣子的字取代)。
這種時(shí)後 normalization 就幫不上忙了。

大部份開發(fā)者應(yīng)該很少遇到這類問(wèn)題吧???

var q = "qu0307u0323".normalize("NFC") // q??
// 經(jīng)過(guò) normalize 還是 qu0307u0323

console.log([...q].length);
// 是 3 不是 1

console.log([..."Z??????????????????A????????L?????G??????????????????????!????????????????"].length);
// 是 74 不是 6

此時(shí)您可以使用正規(guī)式來(lái)移除那些組合的符號(hào)

var sample = "Z??????????????????A????????L?????G??????????????????????!????????????????";
var pattern = /([