なぜ今さら「文字コード」の話なのか?:フロントエンドの現場で遭遇する「見えない爆弾」
やあ。コードレビューをしていて、ふと気づくことがある。「あれ、これ絵文字が入ってきたらバグるんじゃないか?」という予感だ。
JavaScriptの文字列操作は、一見すると直感的で簡単だ。`slice`や`replace`、テンプレートリテラルを使いこなしている中級エンジニアなら、日々のコーディングで困ることは少ないだろう。だが、`charAt`や`charCodeAt`といった「文字の中身」に触れるメソッドを扱うとき、多くのエンジニアがかつて設計された古い仕様の罠にハマってしまう。
今日は、フロントエンドの現場で「なぜか文字化けする」「絵文字だけカウントがずれる」といった泥臭いトラブルを未然に防ぐための、文字コードの深淵について話そうと思う。
—
1. 歴史的遺産:charAt と charCodeAt の限界
まず、現場で非常によく見かけるコードだ。
const str = “A”;
console.log(str.charCodeAt(0)); // 65
これはASCIIコードだから問題ない。だが、JavaScriptの文字列は内部的にUTF-16でエンコードされている。ここで`charCodeAt`や`charAt`を叩くと、「16ビット単位(UTF-16のコードユニット)」で値が返ってくる。
問題は、UTF-16がカバーしきれない「サロゲートペア」だ。絵文字や特殊な漢字は、2つの16ビット値を組み合わせて1つの文字を表現する。これを古いメソッドで触ると、文字の半分だけを切り出したり、意味不明な数値を拾ったりすることになる。
const emoji = “🚀”; // このロケットは実は2つのコードユニットで構成されている
console.log(emoji.length); // 2 になる。1じゃない。
console.log(emoji.charCodeAt(0)); // 55357 (前半部分)
console.log(emoji.charCodeAt(1)); // 56800 (後半部分)
この「長さが2になる」という事実は、文字数制限(バリデーション)の実装で致命的なバグを生む。バックエンドのDBがUTF-8でバリバリ動いているのに、フロントエンド側で`slice`を使って不適切に文字列を切り取ると、壊れたサロゲートペアが送られ、DBエラーや表示崩れを引き起こす。これが現場で最もよく見る「地雷」だ。
—
2. 救世主:codePointAt の重要性
そこで登場するのが `codePointAt` だ。ES2015で導入されたこのメソッドは、サロゲートペアを考慮して、Unicodeの「コードポイント(文字そのものに割り振られた正当なID)」を正しく取得してくれる。
現場での実践的な使い分けとしては、以下のパターンを頭に叩き込んでおいてほしい。
const text = “🚀”;
// charCodeAt はあくまで「16bit単位のデータ」を見る
console.log(text.charCodeAt(0)); // 55357
// codePointAt は「文字そのもののUnicode値」を捉える
console.log(text.codePointAt(0)); // 128640 (🚀の正しいUnicode値)
// 応用:文字列の全文字を安全にイテレートする(現代的な書き方)
const str = “Hello 🚀”;
// for…of はサロゲートペアを考慮してくれるため、最も安全
for (const char of str) {
console.log(`文字: ${char}, コードポイント: ${char.codePointAt(0)}`);
}
—
3. 実務で「泥臭く」勝ちに行くための tips
フロントエンドエンジニアとして、単に仕様を知っているだけでは足りない。プロダクトの品質を守るための「防御的コーディング」を提案する。
罠:`slice` と `substring` の落とし穴
文字数制限で `str.slice(0, 10)` とかやっていないだろうか?もしその文字列に絵文字が含まれていたら、最後の絵文字が半分に割れて「豆腐(□)」が表示されるリスクがある。
解決策:
文字列を扱う際は、極力 `Array.from()` やスプレッド構文で配列に展開してから操作するのが定石だ。
const text = “あいうえ🚀かきく”;
// 悪い例:サロゲートペアを破壊する可能性がある
const badSlice = text.slice(0, 5);
// 良い例:サロゲートペアを「1文字」として認識する
const safeSlice = […text].slice(0, 5).join(”);
console.log(safeSlice); // “あいうえ🚀”
—
結び:仕様の裏側を想像するということ
ブラウザのエンジン(V8など)は、メモリ上で文字列をどう保持するかを高度に最適化している。しかし、我々が扱う文字列は、ユーザーがスマホのキーボードから打ち込む予測不能なデータだ。
「`charCodeAt`でいいや」という妥協は、いつか必ず誰かを泣かせることになる。特にグローバル展開するアプリケーションでは、絵文字だけでなく、多言語の合字(Combining characters)などが混ざり、文字の見た目とデータ上の長さが一致しないケースは日常茶飯事だ。
結論として:
1. `charAt` / `charCodeAt` は、非サロゲートペア(英数字のみ)と確信が持てる時以外は使わない。
2. 文字単位で処理したいなら `[…str]` で配列化するか、`for…of` を使う。
3. 正確なコードポイントが必要な時だけ `codePointAt` を召喚する。
この視点を持つだけで、君の書くコードの信頼性はグッと高まるはずだ。技術の仕様を「暗記」するのではなく、ブラウザという黒い箱の中でデータがどう流れているかを想像する。それこそが、シニアエンジニアへの第一歩だよ。
また何か詰まったら、いつでも聞きに来てくれ。

コメント