【実務・中級編】 文字列のイテレータプロトコルとfor-ofループ – JavaScript実践ガイド

文字列の「裏側」を理解する:JavaScriptのイテレータとサロゲートペアの深淵

フロントエンドの現場で、「文字列をループで回す」なんて作業は、毎日のように行っているはずだ。`for`ループで添字を管理したり、`split(”)`で配列に変換したり。しかし、そのコード、本当に「安全」と言い切れるだろうか?

特にUnicodeが絡む現代のWeb開発において、JavaScriptの文字列操作は時としてエンジニアを裏切る。今回は、JavaScriptの文字列がどのようにイテラブルとして振る舞い、なぜサロゲートペアが「悪魔の種」になり得るのか、そしてプロとしてどう向き合うべきかを深掘りしていく。

—

なぜ `for…of` は「特別」なのか

多くのエンジニアが混同しがちなのが、古き良き `for` ループと、ES6で導入された `for…of` ループの挙動の違いだ。

まず、結論から言おう。`for…of` は、文字列を「コードユニット(UTF-16)」ではなく「コードポイント」の単位で反復する。

従来の `for (let i = 0; i < str.length; i++)` は、メモリ上のUTF-16の箱を一つずつ舐める。一方、`for...of` は、JavaScriptエンジンが「あ、これは一つの文字として扱うべきサロゲートペアだな」と内部的に判定し、適切に切り出してくれる。 これが何を意味するか。絵文字や特殊な記号を扱う際、従来のループではデータが壊れるが、`for...of` なら正しく扱えるということだ。 ---

実践:サロゲートペアを破壊せずに扱う

以下のコードを見てほしい。絵文字の「🍣(寿司)」や「👨‍👩‍👧‍👦(家族)」は、UTF-16では複数のコードユニットで構成されている。

const emojiString = “🍣寿司”;

// 1. 従来のループ(危険!)
// .lengthは「文字数」ではなく「コードユニット数」を返すため、
// サロゲートペアを分割してしまい、文字化け(不明な文字)が発生する
for (let i = 0; i < emojiString.length; i++) { console.log(`従来のループ: ${emojiString[i]}`); // 🍣が分割され、意味をなさない断片が出力される } // 2. for...of ループ(正解!) // イテレータプロトコルが適切に動作し、文字の境界を認識する for (const char of emojiString) { console.log(`for...ofの処理: ${char}`); // 「🍣」「寿」「司」と正しく出力される } この「当たり前のようで、実は裏側で魔法が起きている」挙動こそ、JavaScriptの設計の妙だ。`for...of` は内部的に `String.prototype[Symbol.iterator]` を呼び出し、文字列のイテレータを作成している。このイテレータが、UTF-16のペアを見つけ出し、結合した状態で値を返してくれるのだ。 ---

実務で使えるTips:文字列を「正しく」配列化する

「文字列を配列に変換して `map` を回したい」というシーンは多いだろう。その時、安易に `str.split(”)` を使っていないだろうか?

const text = “🚀爆速開発”;

// 悪い例: splitはサロゲートペアを分断する
const badArray = text.split(”);
console.log(badArray[0]); // 🚀が崩壊した何かが出力される

// 良い例: スプレッド演算子 or Array.from
// これらはイテレータプロトコルを利用するため、サロゲートペアを維持する
const goodArray = […text];
console.log(goodArray[0]); // “🚀” が正しく出力される

スプレッド演算子 `[…]` や `Array.from()` は、実は `for…of` と同じイテレータの仕組みを使っている。つまり、イテレータを理解することは、モダンなJSの配列操作のすべてを制することと同義なのだ。

—

アーキテクトからのアドバイス:泥臭い現実

最後に一つ、現場のシニアとして釘を刺しておきたい。

「イテレータがあるから万事解決」というわけではない。例えば、結合文字(Combining Marks)の扱いだ。例えば「e」にアクセント記号を付けて「é」を作る場合、これらは論理的には1文字だが、Unicode上は2つのコードポイントで構成されることがある。`for…of` はこれらを「2文字」として反復してしまう。

もし、ユーザーが入力した文字列の「見た目上の文字数」を正確にカウントする必要があるなら、`Intl.Segmenter` API(現在主要ブラウザでサポート済み)を使うのが今のベストプラクティスだ。

// 見た目通りの文字数で分割する最新のテクニック
const segmenter = new Intl.Segmenter(‘ja’, { granularity: ‘grapheme’ });
const segments = segmenter.segment(‘é’);

for (const { segment } of segments) {
console.log(segment); // 結合文字も一つの塊として扱われる
}

—

まとめ

1. `for…of` は信頼せよ: コードユニットではなくコードポイントを扱うため、従来のループより遥かに安全だ。
2. `split(”)` は封印せよ: サロゲートペアを破壊するリスクがある。現代のJSではスプレッド演算子か `Array.from()` を選べ。
3. さらに先へ: 「見た目の文字数」を扱うなら、`Intl.Segmenter` という強力な武器があることを忘れるな。

JavaScriptの文字列は、一見シンプルだが非常に奥が深い。仕様を正しく理解し、ブラウザの裏側で何が起きているかを感じ取れるようになれば、君の書くコードの品質は確実に一段上のレベルへ到達するはずだ。

明日からの開発で、ぜひこの視点を取り入れてみてほしい。コードは語る。君がどれだけ深く仕様に向き合っているかを。

コメント

タイトルとURLをコピーしました