JavaScript での絵文字を含む String の文字数カウントは結構ハードだった話。
問題の事象
😇 は直感的には1文字カウントされてほしいが、 length
プロパティを参照すると以下のような結果になる。
'hoge'.length // 4
'ほげ'.length // 2
'😇'.length // 2 ...!?
なぜ?
length
は単に文字数を返しているわけではない。
JavaScript の内部では文字列を UTF-16 形式で保持しており、 lentgh
はこの単位のコード数を返している。
ASCII やひらがなは1つの 16 bit で表されるが、絵文字の多くは サロゲートペア を使って2つの 16 bit で表現される。
前述の例は UTF-16 で以下のように表現される。
String.fromCharCode('0x0068', '0x006F', '0x0067', '0x0065') // hoge
String.fromCharCode('0x307B', '0x3052') // ほげ
String.fromCharCode('0xD83D', '0xDE07') // 😇
見ての通り 😇 はコード2つ分なので、 length
プロパティの値は2となる。
'😇'.length
を1文字としてカウントするには
スプレッド構文 を使って Array に変換した上で length
を取れば1文字(1要素)扱いされる。
[...'😇'].length // 1
これで解決?
文字コードの世界は広かった。
man shrugging と呼ばれる以下の絵文字は次のようにカウントされる。
'🤷♂️'.length // 5 ...!?
[...'🤷♂️'].length // 4 ...!?
この絵文字は 🤷 ( 0xD83E
0xDD37
)と ♂ ( 0x2642
)の組み合わせであり、内部的には以下のように表現される。
String.fromCharCode('0xD83E', '0xDD37', '0x200D', '0x2642', '0xFE0F')
0x200D
は絵文字の結合用のコード、 0xFE0F
は絵文字バリエーション・シーケンスと呼ばれるプレーン文字と絵文字を識別するためのコードらしい。
🤷 は1文字として判定されるものの、後続のコードがそれぞれ1文字判定され、結果4となる。
これらの文字に関してはライブラリを使って対応できるようだが、現状ではシンプルな対応方法はなさそう...。