charCodeAt と codePointAt の違い
文字をコード値に変換する charCodeAt()
と codePointAt()
の違いがよく分かってませんでした。「codePointAt()
使ったほうがいいらしい」程度の認識だったので、ちゃんと調べてみました。
[2020-04-05追記] この記事は2バイト使うサロゲートペア文字について触れていますが、肌の色が異なる絵文字や、家族が合体した絵文字の話をカバーできていません。
そのうちちゃんと調べたら書き直す予定ですが、この記事の内容が全てではないことに注意してください。以下の記事が参考になりました。
Unicode 絵文字にまつわるあれこれ (絵文字の標準とプログラム上でのハンドリング) - Qiita
取り急ぎですみません。
本題
例として『ABあい🍅🐤!
』という文字列があるとします。文字数は見ての通り7文字です。
この文字列を length
ぶんループ処理しながら、ひとつずつ charCodeAt()
と codePointAt()
に通してみます。
charCodeAt()
と codePointAt()
で結果が異なってます。また、結果の要素数から、実際のところ*『ABあい🍅🐤!
』の length
は9*であったことが分かります。
なぜこうなるのか、無い頭しぼって図を描いてみました。実行結果と見比べてみてください。
そもそも……。
charCodeAt()
が返却するのはUTF-16コードでした。- UTF-16コードは、ある文字列をUTF-16で表現した場合のコード値です。UTF-16は、16ビットのコードを使ってUnicode文字を表現するエンコード方式です。
- UTF-16コードは 0 ~ 65,535 (0x0 ~ 0xFFFF) の範囲をとります。
一方で……。
codePointAt()
が返却するのはUnicodeコードポイントでした。- Unicodeコードポイントは、純粋にUnicodeで表現できる文字ひとつひとつに振られている番号です。
- Unicodeコードポイントは 0 ~ 1,114,111 (0x0 ~ 0x10FFFF) の範囲をとります。
- Unicodeコードポイントの 0 ~ 65,535 は、UTF-16コードと同じ文字を指してます。
すると、*UTF-16コードが65,536個しかないのに対して、Unicodeで表現したい文字は1,114,112個もあることになります。*なので実際には、ひとつのUTF-16コードで表現できない文字については、UTF-16コードを2つ使うことで表現してるそうです。いわゆるサロゲートペアです。
先ほどの図でサロゲートペアを説明したのが次の図となります。サロゲートペア的にはコード値が16進数のほうがキリがいいので、こちらは16進表記です。
ここでは絵文字しか出てきませんが、絵文字以外にもサロゲートペア使用文字は漢字とか色々あります。漢字では𩸽 (ほっけ) が有名みたいですね。
このあたり全般の理解については、下記のサイト様が大変参考になりました。
で、JavaScriptの内部的には、文字列はUTF-16で扱われてるらしいです。これは、元を辿っていったらECMAScriptの仕様書に書いてありました。
ECMAScript® 2018 Language Specification
つまり文字列の length
がUTF-16コードの数とイコールになるのは、そもそもJavaScriptが文字列をUTF-16で扱ってるからです。『ABあい🍅🐤!
』の length
が9だったのは、🍅
と 🐤
がサロゲートペアを使わないと表現できない (UTF-16コードが2つ必要な) 文字だったせいです。
また、サロゲートペア文字であることがわかるのは、文字の1バイト目を見たときです。2バイト目だけ見ても、それがサロゲートペア文字であることはわかりません。
i = 4
のとき、charCodeAt()
はサロゲートペア文字の上位1バイトだけのコード値 55356 を返しますが、codePointAt()
はこれがサロゲートペア文字であると判断し、文字丸ごとのコードポイント 127813 を返します。
i = 5
のとき、charCodeAt()
はサロゲートペア文字の下位1バイトだけのコード値 57157 を返します。codePointAt()
も、i = 5
だけ見てもサロゲートペア文字だとはわからないので、同様にコードポイント 57157 を返します。
同じことが i = 6
と i = 7
のときも言えます。
codePointAt()
はサロゲートペア文字でも単一のコード値が取得できて便利ですが、「一文字ずつ処理したい」という観点では、単純に length
でループするだけではダメなようです。
じゃあ『ABあい🍅🐤!
』を本当に7文字として処理するにはどうすればいいの!? という点については、ネットで調べると素晴らしい記事がいっぱい出くるので、そちらにお任せします。