「文字数カウント」で本当に数えたいものは何か
まず、ここをはっきりさせたいです。
「文字数を数える」と言っても、実は意味が分かれます。
画面上で「何文字入力されたか」を知りたい。
DBのカラム制限(バイト数)に収まるかを知りたい。
「最大 100 文字まで」といったバリデーションをしたい。
そして JavaScript では、「見た目の1文字」と string.length が必ずしも一致しない、という落とし穴があります。
ここをちゃんと理解しておくと、一気に“業務で使える”文字数カウントになります。
基本の length と、そのままでは危ない理由
str.length が数えているもの
JavaScript の文字列は、内部的には UTF-16 で表現されています。str.length が数えているのは、「UTF-16 のコードユニットの数」です。
"ABC".length; // 3
"あいう".length; // 3
JavaScriptここまでは直感どおりです。
ところが、絵文字や一部の漢字など、「サロゲートペア」と呼ばれる文字が出てくると話が変わります。
"😊".length; // 2
"U+20BB7".length; // 2(旧字体の「吉」いわゆる「つちよし」の吉など)
JavaScript※「つちよし」の吉を記載できないのでUTFで書いています。
見た目は「1文字」なのに、length は 2 になります。
つまり、「画面上の1文字数」としては length は信用できない、ということです。
「見た目の1文字数」を数える安全な方法
for...of や Array.from を使う
「見た目の1文字」を数えたいときは、コードユニットではなく“コードポイント”単位で数える必要があります。
モダンな JavaScript では、for...of や Array.from を使うと、サロゲートペアも1文字として扱えます。
function countChars(str) {
if (str == null) return 0;
let count = 0;
for (const _ch of String(str)) {
count++;
}
return count;
}
JavaScriptここが重要ポイントです。
for (const _ch of String(str)) は、「文字列を“1文字ずつ”取り出す」構文。
このとき、絵文字やサロゲートペアも1文字として扱われる。
そのたびに count++ していけば、「見た目の1文字数」が数えられる。
動きのイメージはこうです。
"ABC".length; // 3
countChars("ABC"); // 3
"あいう".length; // 3
countChars("あいう"); // 3
"😊".length; // 2
countChars("😊"); // 1
"A😊B".length; // 4
countChars("A😊B"); // 3
JavaScript「入力欄は最大 20 文字まで」といった制限を、ユーザーの感覚に合わせてかけたいときは、必ずこういう「コードポイント単位」のカウントを使うべきです。
「バイト数」を知りたい場合(DB制限など)
文字数ではなく「何バイトか」が問題になるケース
RDB のカラム定義や、外部システムとの連携では、
「文字数」ではなく「バイト数」で制限されていることがあります。
例として、「UTF-8 で最大 255 バイトまで」といった制限です。
この場合、「全角1文字=3バイト(UTF-8)」などを意識する必要があります。
簡易的な UTF-8 バイト数カウント
ブラウザ環境なら、TextEncoder を使うと簡単に UTF-8 のバイト数を数えられます。
function countUtf8Bytes(str) {
if (str == null) return 0;
const s = String(str);
const encoder = new TextEncoder();
const bytes = encoder.encode(s);
return bytes.length;
}
JavaScript動きはこうなります。
countUtf8Bytes("ABC"); // 3
countUtf8Bytes("あ"); // 3(UTF-8 では全角1文字=3バイト)
countUtf8Bytes("😊"); // 4(絵文字は4バイトになることが多い)
JavaScriptDB のカラム制限が「UTF-8 で N バイトまで」のような場合は、countChars ではなく countUtf8Bytes を使ってチェックする必要があります。
実務での使いどころと設計のポイント
「何を基準に制限するか」を先に決める
ここが一番大事です。
画面上の「見た目の文字数」で制限したいのか。
DB の「バイト数」で制限したいのか。
これを曖昧にしたまま length を使うと、
絵文字1つで「2文字扱い」になったり、
DB に入らないのにフロントではOKになったり、といったズレが出ます。
なので、ユーティリティをこう分けておくときれいです。
export function countChars(str) { ... } // 見た目の1文字数
export function countUtf8Bytes(str) { ... } // UTF-8 のバイト数
JavaScriptそして、呼び出し側で「どちらを使うか」を明示的に選ぶようにします。
バリデーションとセットで使う
例えば、「最大 20 文字まで」の入力欄ならこう書けます。
const maxChars = 20;
const value = input.value;
const length = countChars(value);
if (length > maxChars) {
showError(`最大 ${maxChars} 文字までです(現在 ${length} 文字)。`);
}
JavaScript「何文字まで」と「今何文字か」を一緒に見せてあげると、ユーザーにも親切です。
ちょっとだけ手を動かしてみる
コンソールで、次の順番で試してみてください。
"😊".length;
countChars("😊");
"A😊B".length;
countChars("A😊B");
countUtf8Bytes("A");
countUtf8Bytes("あ");
countUtf8Bytes("😊");
JavaScript「length と countChars の違い」
「文字によってバイト数が違う感覚」
を、自分の目で確かめてみてください。
そのうえで、自分のプロジェクトに
export function countChars(str) { ... }
export function countUtf8Bytes(str) { ... }
JavaScriptの2本を置いて、
「画面の文字数制限 → countChars」
「DBや外部連携のバイト制限 → countUtf8Bytes」
という使い分けをルール化してみてください。
それだけで、あなたの「文字数カウント」は
なんとなくの length 依存から、
ユーザーの感覚とシステム制約を両方意識した“業務レベルのユーティリティ”に変わります。
