JavaScriptで正確に文字数をカウントする

どうも。最近は仕事ではめっきりTypeScriptを書いています。そんな中で、え! ? 文字数カウントで単純にString.lengthじゃダメなの! ? と思ったので備忘録的に書き残しておきます。

概要

  • JavaScriptでは文字列のカウントはlengthメソッドでは不十分
  • サロゲートペアを含む文字列のカウントがズレる
  • 代替の方法と正規表現のカウントについて

普通にやる文字数カウント

TypeScriptを含むJavaScriptのフロントエンド環境では、例えばフォームの文字数によるバリデーションなどを代表として文字列をカウントする状況があると思います。

特に深く考えず文字数のカウントをする場合は、String.lengthと書くと思います。

しかし、実はこのlengthは厳密には文字数ではなくコードユニット数です。これで問題になるのがUnicodeでサロゲートペアを含む文字列です。

では、ちょっと動きを見てみます。

'aeiou'.length
// => 5
'あいうえお'.length
// => 5
'ニコ😀'.length
// => 4
'𠮷野屋'.length
// => 4

となります。

絵文字だけならまだしも一部、漢字の異体字も2文字判定になります。

これが困るケースはフロントエンドでのカウントとサーバーサイドやDB側でのカウントにズレがある場合ですね。

正確にやる文字数カウント

では、どうすればいいかというと結論から言えば[...string].lengthでカウントする方法です。

[...'aeiou'].length
// => 5
[...'あいうえお'].length
// => 5
[...'ニコ😀'].length
// => 3
[...'𠮷野屋'].length
// => 3

解説すると、配列の中で文字列をスプレッド構文で展開、配列の要素数を数えることで正確な文字数を出しています。

ちなみにこの方法はMDNにも案内されています。

ちなみに注意点としては、スプレッド構文はES6以降の対応なのでレガシーなブラウザでも動作させるならbabelなどのpolyfillで対応が必要になる点はご注意ください。

では正規表現でのカウントは?

正規表現では文字数の指定が可能です。例えば5文字以上10文字以下の文字列を判定するとしたら、

/^.{5,10}$/.test('aeiouあいうえお')
// => true

ですね。これに前述のとおり絵文字などが入るとどうでしょう?

/^.{5,10}$/.test('aeiouあいうえ😀')
// => false

となり、JS(TS)の正規表現での文字数指定はlengthと同じカウントということがわかりました。

といっても大抵の場合、正規表現で文字数まで判定する場合はパスワードやemailアドレス、電話番号など、アルファベットや数字に限定する場合がほとんどなのでそこまで心配する必要もないことが多いです。

/^\w{5,10}$/.test('aeiouあいうえお')
// => false
/^\w{5,10}$/.test('aeiouuoiea')
// => true

ただやはり知らないと思わぬ罠にハマることもあるので気をつける必要はありそうです。

もしどうしても正規表現で入力文字を検証しつつ、同時に文字数にも制限を設け、しかも文字列にサロゲートペアを含む文字列を入るようなケースがあるとしたら、正規表現と前述の正確な文字列の算出を併用して、どちらもパスするようなら、みたいな関数を作るのが確実そうですね。

感想

今回はJS(TS)での文字数の正確なカウントについての注意でした。実はこの記事を書きはじめるまでMDNにスプレッド構文による解決法が記載されていることを知りませんでした。

一度配列化するのは少しhackyな気もしますが、逆に言えばその方法が適切だという裏付けでもあり、自信を持って使えそうで良かったです。

この知識はUnicode、特に我々のような非アルファベット圏のユーザーには欠かせない注意ポイントですね。

Sentryを入れてRailsのエラー監視ををする(sentry-ruby, sentry-rails)Visual Studio Code のキーボードショートカットで発火してるコマンドを探す方法