このセクションでは、文字列の内部構造をより深く掘り下げます。絵文字、珍しい数学記号や象形文字、その他の珍しい記号を扱う予定がある場合、この知識は役立ちます。
既に知っているように、JavaScriptの文字列はUnicodeに基づいています。各文字は1~4バイトのバイトシーケンスで表されます。
JavaScriptでは、16進数のUnicodeコードを指定して、次の3つの表記のいずれかを使用して文字を文字列に挿入できます。
-
\xXX
XX
は00
からFF
までの値を持つ2桁の16進数でなければなりません。その場合、\xXX
はUnicodeコードがXX
である文字です。\xXX
表記は2桁の16進数しかサポートしていないため、最初の256個のUnicode文字に対してのみ使用できます。この最初の256文字には、ラテンアルファベット、最も基本的な構文文字、その他いくつかが含まれています。たとえば、
"\x7A"
は"z"
(UnicodeU+007A
)と同じです。alert( "\x7A" ); // z alert( "\xA9" ); // ©, the copyright symbol
-
\uXXXX
XXXX
は0000
からFFFF
までの値を持つ正確に4桁の16進数でなければなりません。その場合、\uXXXX
はUnicodeコードがXXXX
である文字です。U+FFFF
より大きいUnicode値を持つ文字もこの表記で表すことができますが、この場合はサロゲートペアと呼ばれるものを使用する必要があります(サロゲートペアについては、この章の後半で説明します)。alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation alert( "\u044F" ); // я, the Cyrillic alphabet letter alert( "\u2191" ); // ↑, the arrow up symbol
-
\u{X…XXXXXX}
X…XXXXXX
は、0
から10FFFF
(Unicodeで定義されている最高のコードポイント)までの1~6バイトの16進数値でなければなりません。この表記を使用すると、既存のすべてのUnicode文字を簡単に表すことができます。alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode)
サロゲートペア
頻繁に使用される文字はすべて、2バイトコード(4桁の16進数)を持っています。ほとんどのヨーロッパ言語の文字、数字、および基本的な統一CJK統合漢字(CJK - 中国語、日本語、韓国語の表記体系から)は、2バイト表現を持っています。
当初、JavaScriptはUTF-16エンコーディングに基づいており、文字ごとに2バイトしか許可されていませんでした。しかし、2バイトでは65536個の組み合わせしか許可されず、Unicodeのすべての可能な記号には不十分です。
そのため、2バイト以上を必要とする珍しい記号は、「サロゲートペア」と呼ばれる2バイトの文字のペアでエンコードされます。
副作用として、このような記号の長さは2
になります。
alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X
alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY
alert( '𩷶'.length ); // 2, a rare Chinese character
これは、JavaScriptの作成時にはサロゲートペアが存在せず、そのため言語によって正しく処理されないためです!
上記の文字列にはそれぞれ1つの記号がありますが、length
プロパティは長さ2
を示しています。
記号を取得することもトリッキーな場合があります。これは、ほとんどの言語機能がサロゲートペアを2つの文字として扱うためです。
たとえば、ここでは出力に2つの奇妙な文字が表示されます。
alert( '𝒳'[0] ); // shows strange symbols...
alert( '𝒳'[1] ); // ...pieces of the surrogate pair
サロゲートペアの部分は、互い nélkül意味を持ちません。そのため、上記の例のアラートは実際にはゴミを表示します。
技術的には、サロゲートペアはコードでも検出できます。文字のコードが0xd800..0xdbff
の範囲にある場合、それはサロゲートペアの最初の部分です。次の文字(2番目の部分)のコードは0xdc00..0xdfff
の範囲内にある必要があります。これらの範囲は、標準によってサロゲートペア専用に予約されています。
そのため、String.fromCodePointとstr.codePointAtというメソッドがJavaScriptに追加され、サロゲートペアを処理できるようになりました。
これらは本質的にString.fromCharCodeとstr.charCodeAtと同じですが、サロゲートペアを正しく処理します。
違いはここにあります。
// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳:
alert( '𝒳'.charCodeAt(0).toString(16) ); // d835
// codePointAt is surrogate-pair aware
alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair
つまり、位置1から取得する場合(ここではむしろ正しくありません)、どちらもペアの2番目の部分のみを返します。
alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3
alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3
// meaningless 2nd half of the pair
サロゲートペアの処理方法の詳細については、後の章反復可能オブジェクトで説明します。おそらく、そのため特別なライブラリもありますが、ここで推奨できるほど有名なものは何もありません。
文字列を任意の位置で分割することはできません。たとえば、str.slice(0, 4)
を取り、それが有効な文字列であると期待することはできません。
alert( 'hi 😂'.slice(0, 4) ); // hi [?]
ここでは、出力にゴミ文字(スマイリーサロゲートペアの前半)が表示されます。
サロゲートペアを確実に操作する予定がある場合は、注意してください。大きな問題ではないかもしれませんが、少なくとも何が起こっているのかを理解する必要があります。
発音区別符号と正規化
多くの言語には、基本文字の上に/下にマークが付いた複合文字があります。
たとえば、文字a
はこれらの文字の基本文字になる可能性があります:àáâäãåā
。
最も一般的な「複合」文字には、Unicodeテーブルに独自のコードがあります。しかし、可能な組み合わせが多すぎるため、すべてではありません。
任意の合成をサポートするために、Unicode標準では、複数のUnicode文字を使用できます。基本文字の後に、それを「装飾する」1つ以上の「マーク」文字が続きます。
たとえば、S
の後に特別な「上点」文字(コード\u0307
)を付けると、Ṡのように表示されます。
alert( 'S\u0307' ); // Ṡ
文字の上に(または下に)別のマークが必要な場合でも問題ありません。必要なマーク文字を追加するだけです。
たとえば、「下点」文字(コード\u0323
)を追加すると、「上点と下点が付いたS」になります:Ṩ
。
例:
alert( 'S\u0307\u0323' ); // Ṩ
これは大きな柔軟性を提供しますが、興味深い問題も発生します。2つの文字は視覚的には同じように見える場合がありますが、異なるUnicode合成で表される場合があります。
例:
let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below
let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above
alert( `s1: ${s1}, s2: ${s2}` );
alert( s1 == s2 ); // false though the characters look identical (?!)
これを解決するために、「Unicode正規化」アルゴリズムがあり、各文字列を単一の「標準」形式にします。
これはstr.normalize()によって実装されています。
alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
面白いことに、私たちの状況では、normalize()
は実際には3文字のシーケンスを1つにまとめます:\u1e68
(2つの点が付いたS)。
alert( "S\u0307\u0323".normalize().length ); // 1
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
実際には、常にそうとは限りません。その理由は、記号Ṩ
は「十分に一般的」であるため、Unicodeの作成者はそれをメインテーブルに含め、コードを与えたためです。
正規化ルールとバリアントの詳細については、Unicode標準の付録に記載されています。Unicode正規化形式しかし、ほとんどの実用的な目的のために、このセクションの情報で十分です。
コメント
<code>
タグを使用し、複数の行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。