Tato kapitola se hloubÄji zabývá vnitÅnà reprezentacà ÅetÄzců. Tato znalost vám bude užiteÄná, jestliže plánujete pracovat s emoji, vzácnými matematickými nebo hieroglyfickými znaky nebo jinými vzácnými symboly.
Jak už vÃme, ÅetÄzce v JavaScriptu jsou založeny na Unicode: každý znak je reprezentován posloupnostà 1 až 4 bytů.
JavaScript nám umožÅuje vložit znak do ÅetÄzce specifikacà jeho hexadecimálnÃho kódu v Unicode pomocà jednoho z následujÃcÃch tÅà zápisů:
-
\xXXXXmusà být dvÄ hexadecimálnà ÄÃslice s hodnotou mezi00aFF, pak\xXXje znak, jehož kód v Unicode jeXX.Protože zápis
\xXXpodporuje jen dvÄ hexadecimálnà ÄÃslice, může být použit jen pro prvnÃch 256 znaků Unicode.TÄchto prvnÃch 256 znaků obsahuje latinskou abecedu, vÄtÅ¡inu základnÃch syntaktických znaků a nÄkteré dalÅ¡Ã. NapÅÃklad
"\x7A"je totéž jako"z"(UnicodeU+007A).alert( "\x7A" ); // z alert( "\xA9" ); // ©, symbol copyrightu -
\uXXXXXXXXmusà být pÅesnÄ 4 hexadecimálnà ÄÃslice s hodnotou mezi0000aFFFF, pak\uXXXXje znak, jehož kód v Unicode jeXXXX.TÃmto zápisem mohou být reprezentovány i znaky, jejichž hodnoty v Unicode jsou vÄtšà než
U+FFFF, ale v takovém pÅÃpadÄ musÃme použÃt takzvaný zástupný pár (o zástupných párech pohovoÅÃme pozdÄji v této kapitole).alert( "\u00A9" ); // ©, totéž jako \xA9 s použitÃm 4-ciferného hexadecimálnÃho zápisu alert( "\u044F" ); // Ñ, pÃsmeno z kyrilice (azbuky) alert( "\u2191" ); // â, symbol Å¡ipky nahoru -
\u{Xâ¦XXXXXX}Xâ¦XXXXXXmusà být hexadecimálnà hodnota 1 až 6 bytů mezi0a10FFFF(nejvyššà kódová hodnota definovaná v Unicode). Tento zápis nám umožÅuje snadno reprezentovat vÅ¡echny existujÃcà znaky v Unicode.alert( "\u{20331}" ); // 佫, vzácný ÄÃnský znak (dlouhý Unicode) alert( "\u{1F60D}" ); // ð, symbol usmÃvajÃcà se tváÅe (dalšà dlouhý Unicode)
Zástupné páry
VÅ¡echny Äasto použÃvané znaky majà 2-bytové kódy (4 hexadecimálnà ÄÃslice). PÃsmena ve vÄtÅ¡inÄ evropských jazyků, ÄÃslice a základnà sjednocené ideografické sady CJK (CJK â pro ÄÃnské, japonské a korejské pÃsemné soustavy) majà 2-bytovou reprezentaci.
JavaScript byl původnÄ založen na kódovánà UTF-16, které umožÅovalo jen 2 byty na znak. AvÅ¡ak 2 byty umožÅujà jen 65536 kombinacÃ, a to pro každý možný symbol v Unicode nestaÄÃ.
Vzácné symboly, které vyžadujà vÃce než 2 byty, jsou tedy zakódovány dvojicà 2-bytových znaků nazývanou âzástupný párâ (âsurrogate pairâ).
VedlejÅ¡Ãm efektem je, že délka takových symbolů je 2:
alert( 'ð³'.length ); // 2, VELKÃ X V MATEMATICKÃM PÃSMU
alert( 'ð'.length ); // 2, TVÃÅ SE SLZAMI RADOSTI
alert( 'ð©·¶'.length ); // 2, vzácný ÄÃnský znak
Je to proto, že v dobÄ, kdy JavaScript vznikl, jeÅ¡tÄ zástupné páry neexistovaly, a proto nejsou jazykem správnÄ zpracovávány!
Ve skuteÄnosti máme v každém z uvedených ÅetÄzců jediný symbol, ale vlastnost length ukazuje délku 2.
RovnÄž zÃskánà symbolu může být problematické, jelikož vÄtÅ¡ina prvků jazyka zacházà se zástupnými páry jako se dvÄma znaky.
NapÅÃklad zde vidÃme na výstupu dva podivné znaky:
alert( 'ð³'[0] ); // zobrazuje zvláštnà symboly...
alert( 'ð³'[1] ); // ...Äásti zástupného páru
Äásti zástupného páru nemajà jedna bez druhé žádný význam. V uvedeném pÅÃkladu se tedy ve skuteÄnosti zobrazà nesmysly.
Technicky lze zástupné páry detekovat podle jejich kódu: jestliže znak má kód v intervalu 0xd800..0xdbff, pak je to prvnà Äást zástupného páru. Dalšà znak (druhá Äást) musà mÃt kód v intervalu 0xdc00..0xdfff. Tyto intervaly jsou ve standardu exkluzÃvnÄ rezervovány pro zástupné páry.
Proto byly do JavaScriptu pÅidány metody String.fromCodePoint a ÅetÄzec.codePointAt, které si dokážà se zástupnými páry poradit.
Jsou v zásadÄ stejné jako String.fromCharCode a ÅetÄzec.charCodeAt, ale se zástupnými páry zacházejà správnÄ.
Zde vidÃme rozdÃl:
// charCodeAt nezná zástupné páry, takže vydá kód pro 1. Äást:
alert( 'ð³'.charCodeAt(0).toString(16) ); // d835
// codePointAt zná zástupné páry
alert( 'ð³'.codePointAt(0).toString(16) ); // 1d4b3, pÅeÄte obÄ Äásti zástupného páru
OvÅ¡em naÄÃtáme-li od pozice 1 (a to je zde dosti nekorektnÃ), pak obÄ vrátà jen druhou Äást páru:
alert( 'ð³'.charCodeAt(1).toString(16) ); // dcb3
alert( 'ð³'.codePointAt(1).toString(16) ); // dcb3
// nesmyslná 2. Äást páru
Dalšà způsoby, jak si se zástupnými páry poradit, naleznete v kapitole Iterovatelné objekty. PravdÄpodobnÄ pro to existujà i speciálnà knihovny, ale žádná nenà dostateÄnÄ známá na to, abychom ji tady doporuÄili.
Nemůžeme jen tak rozdÄlit ÅetÄzec na libovolné pozici, napÅ. volat ÅetÄzec.slice(0, 6) a oÄekávat, že to bude platný ÅetÄzec, napÅ.:
alert( 'ahoj ð'.slice(0, 6) ); // ahoj [?]
Zde vidÃme na výstupu nesmyslný znak (prvnà polovinu zástupného páru úsmÄvu).
MÄjte to na pamÄti, jestliže zamýšlÃte zodpovÄdnÄ pracovat se zástupnými páry. Nemusà to být velký problém, ale aspoÅ byste mÄli rozumÄt tomu, co se dÄje.
Diakritická znaménka a normalizace
Mnoho jazyků obsahuje symboly, které se skládajà ze základnÃho znaku a znaménka nad nebo pod nÃm.
NapÅÃklad pÃsmeno a může být základnÃm znakem pro tyto znaky: à áâäãåÄ.
VÄtÅ¡ina bÄžných âsloženýchâ znaků má v tabulce Unicode svůj vlastnà kód. Ne vÅ¡ak vÅ¡echny, protože možných kombinacà je pÅÃliÅ¡ mnoho.
Abychom mohli použÃvat libovolné složeniny, standard Unicode nám umožÅuje použÃt nÄkolik znaků Unicode za sebou: základnà znak následovaný jednÃm nebo vÃce znaky âznaménekâ, která jej âozdobÃâ.
NapÅÃklad máme-li S následované speciálnÃm znakem âteÄka nahoÅeâ (kód \u0307), zobrazà se jako SÌ.
alert( 'S\u0307' ); // SÌ
PotÅebujeme-li dalšà znaménko nad pÃsmenem (nebo pod nÃm) â žádný problém, jednoduÅ¡e pÅidáme potÅebný znak znaménka.
NapÅÃklad pÅipojÃme-li znak âteÄka doleâ (kód \u0323), budeme mÃt âS s teÄkami nahoÅe a doleâ: SÌÌ£.
PÅÃklad:
alert( 'S\u0307\u0323' ); // SÌÌ£
To nám poskytuje velkou flexibilitu, ale také zajÃmavý problém: dva znaky mohou vizuálnÄ vypadat stejnÄ, ale být reprezentovány různými složeninami z Unicode.
PÅÃklad:
let s1 = 'S\u0307\u0323'; // SÌÌ£, S + teÄka nahoÅe + teÄka dole
let s2 = 'S\u0323\u0307'; // SÌ£Ì, S + teÄka dole + teÄka nahoÅe
alert( `s1: ${s1}, s2: ${s2}` );
alert( s1 == s2 ); // false, tÅebaže znaky vypadajà stejnÄ (?!)
ÅeÅ¡enà nám poskytuje algoritmus ânormalizace Unicodeâ, který pÅevádà každý ÅetÄzec do jednoduché ânormálnÃâ formy.
Je implementován metodou ÅetÄzec.normalize().
alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
Je humorné, že v našà situaci normalize() ve skuteÄnosti spojà posloupnost tÅà znaků do jednoho: \u1e68 (S se dvÄma teÄkami).
alert( "S\u0307\u0323".normalize().length ); // 1
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
V realitÄ vÅ¡ak tomu tak nenà vždy. Důvodem je, že symbol Ṩ je âdostateÄnÄ bÄžnýâ, takže jej tvůrci Unicode zahrnuli do hlavnà tabulky a pÅiÅadili mu kód.
Pokud se chcete o pravidlech a variantách normalizace dozvÄdÄt vÃc, jsou popsána v pÅÃloze standardu Unicode: NormalizaÄnà formy Unicode, ale pro vÄtÅ¡inu praktických úÄelů je informace z tohoto Älánku dostaÄujÃcÃ.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)