こんにちは!JavaScriptの世界へようこそ。フロントエンドの現場で日々コードを書いていると、文字や文字列の扱いって一見地味に見えて、実は一番ハマりやすい「魔物」だったりするんですよね。
特にユーザーが入力したフォームのバリデーションや、検索機能を作っているとき。「あれ? 完全に同じ文字を入力したはずなのに、なぜか『一致しません』って判定されて弾かれる……!」なんて理不尽なバグに遭遇したことはありませんか? 私は現役時代、これで何度も夜食のカップラーメンを伸びさせてしまいました。
その原因、実は「目に見えない文字の裏側の姿」に隠されているんです。今回は、JavaScriptでの文字列操作の裏側でこっそり活躍している、ちょっぴりディープだけど絶対に知っておくべき「Unicode正規化(NFC / NFD / NFKC / NFKD)」の扉を、一緒にゆっくり開けていきましょう。
難しい専門用語はなるべく身近な例えに置き換えていくので、どうか肩の力を抜いてリラックスして読んでくださいね。
—
1. なぜ「同じ文字」なのにコンピュータは「違う」と判断するのか?
まず、私たちが普段何気なく使っているパソコンやスマホの画面を思い浮かべてみてください。
例えば、「が」という文字。
これ、よく見ると「か」という本体に、「゛(濁点)」というパーツが合体してできていますよね。
私たち人間の目から見ると、「が」はどこからどう見ても「が」という一つの文字です。でも、コンピュータの世界(Unicodeの仕組み)では、この「が」を表現する方法が、実は2パターン用意されてしまっているんです。
1. 「が」という完成された文字を1文字としてドカンと登録するスタイル
2. 「か」という文字と、「゛(濁点)」というパーツを別々に用意して、画面上でくっつけて表示するスタイル
これを身近な例えで言うなら……そうですね、「完成品のレゴブロックの車」と「バラバラのパーツの箱+説明書」みたいなものです。
- 完成品の車(NFC):そのままお部屋に飾れるし、一目で車だとわかる。
- バラバラのパーツ(NFD):組み立てれば同じ車になるけれど、引き出しの中では「タイヤ」と「ボディ」が別々に散らばっている状態。
コンピュータにとって、この2つは「中身のデータ構造(パーツの数や並び順)」がまったく別物です。だから、JavaScriptで素朴に `===`(厳密等価演算子)で比較すると、人間が「同じじゃん!」と思っても、コンピュータは「いや、片方は完成品で、もう片方はバラバラのパーツだから別物です!」と冷たく弾き返してしまうわけなんですね。
つまずきやすいポイントですが、大丈夫です。この「見えない違い」を綺麗に整えて、同じ形に統一してあげる魔法の仕組みこそが「Unicode正規化」なのです。
—
2. 4つの正規化形式(NFC, NFD, NFKC, NFKD)を優しく解剖する
JavaScript(というかECMAScript)には、この正規化を行うために `String.prototype.normalize()` という心強いメソッドが用意されています。
引数に渡す文字列によって、4つのモード(NFC、NFD、NFKC、NFKD)を切り替えることができるのですが、名前が似ていて呪文のようですよね。ひとつずつ、おやつの買い物に例えて優しく紐解いていきましょう。
① NFC(Normalization Form C:Canonical Composition)
- イメージ:綺麗にパッケージされた「おトクな詰め合わせパック」
- 動き: バラバラになっているパーツを、可能な限り「1つの完成された文字(合成文字)」にガッチリ合体させます。
- いつ使う?: 基本はこれ一択でOKです! Web制作やアプリ開発で文字を保存・比較・表示するときは、大体NFCを選んでおけば間違いありません。世界標準として一番ポピュラーな形です。
② NFD(Normalization Form D:Canonical Decomposition)
- イメージ:買ったばかりのプラモデルの「パーツ全バラし状態」
- 動き: 1つの文字としてまとまっているものを、あえて「本体」と「濁点やアクセント記号」に分解します。
- いつ使う?: 正直、Webの一般的な画面制作で自発的に使う機会はほとんどありません。ただ、Macのファイルシステム(HFS+など)の内部で特殊な使われ方をしていたりするので、「あ、あえてバラバラにするモードね」と知っておくだけで十分です。
—
ここで、「C」と「D」のペアが出てきましたね。
C は Composition(合成する)、D は Decomposition(分解する) と覚えると、一気に親しみやすくなりますよ。
そして、さらに文字の「見た目や全角・半角」のこだわりまでお掃除してくれるのが、もう一つのペア 「K」 が付く仲間たちです。
③ NFKC(Normalization Form KC:Compatibility Composition)
- イメージ:「大掃除ですっきり綺麗に整理整頓されたお部屋」
- 動き: NFCの合体機能に加えて、「全角の英数(ABC、123)」を「半角の英数(ABC、123)」に統一したり、見た目が似ている互換文字をきれいにお片付け(互換分解 + 合成)してくれます。
- いつ使う?: ユーザーがフォームに全角英数でメールアドレスやパスワード、型番などを入力してしまったとき!「半角で統一してデータベースに入れたいな」というシーンで大活躍します。
④ NFKD(Normalization Form KD:Compatibility Decomposition)
- イメージ:「お部屋の掃除をした挙句、家具のパーツまですべてバラバラにしてダンボールに詰めた状態」
- 動き: すべての文字を分解した上で、互換文字の整理も行います。
- いつ使う?: かなり特殊なテキスト検索エンジンを作る際や、表記ゆれを極限までなくして文字の「骨組み」だけを抽出したいマニアックな場面で使われます。実務で初心者の方が直接触る機会はまずありません。
—
3. 実践!JavaScriptでのコードの書き方
理屈が分かったところで、実際にJavaScriptでどう書くのか、エディタにコピペしてすぐ試せるコードを見てみましょう。
今回は、先ほどお話した「見た目は同じ『が』だけど、中身のデータが違う」という意地悪なケースを再現してみます。
// 【ケーススタディ】
// 変数A: 「が」が最初から1文字で完成しているデータ (NFC)
const textA = “\u304C”; // ひらがなの「が」のユニコード
// 変数B: 「か」と「濁点(゙)」が別々に用意されているデータ (NFD)
const textB = “\u304B\u3099”; // 「か」 + 濁点の結合文字
// 人間の目にはどちらも「が」に見えますが……?
console.log(“そのまま比較:”, textA === textB);
// 結果: false (コンピュータにとっては別物なので弾かれます!)
// — ここで救世主「normalize()」の登場です! —
// 比較する前に、両方の文字を「NFC(標準の形)」に整えます
const normalizedA = textA.normalize(“NFC”);
const normalizedB = textB.normalize(“NFC”);
console.log(“正規化して比較:”, normalizedA === normalizedB);
// 結果: true (やったー!無事に一致しました!)
どうでしょう? `textA.normalize(“NFC”)` のように、たった一行のメソッドを挟むだけで、あの厄介な「見えない不一致バグ」を綺麗に解決できちゃうんです。
もう一つの実用例:全角・半角のゆれを直す NFKC
ユーザー登録画面などで、「全角の数字やアルファベット」で入力されて困った経験はありませんか? そんなときも `NFKC` がそっとあなたを助けてくれます。
// ユーザーがうっかり全角で「12345」と入力したとします
const userInput = “12345”;
// NFKCで正規化してみましょう
const cleanedInput = userInput.normalize(“NFKC”);
console.log(cleanedInput);
// 結果: “12345” (綺麗な半角数字に大変身!)
データベースの検索やバリデーションの処理の頭にこの `normalize(“NFKC”)`(またはNFC)をそっと仕込んでおくだけで、ユーザーからの「なんか登録できないんだけど!」という問い合わせを未然に防ぐことができるようになります。これぞ、デキるフロントエンドエンジニアの隠し味ですね。
—
4. おわりに
Unicode正規化、いかがでしたでしょうか?
最初は「NFC?NFKC?なにそれ呪文?」と身構えてしまったかもしれませんが、裏側にあるのは「コンピュータに同じ文字を正しく同じと分かってもらうための、優しいたしなみ」なんです。
実務でコードを書くときは、基本的には `str.normalize(‘NFC’)`(あるいは全角半角も揃えたいなら `NFKC`)を思い出して、文字列の比較や保存の前にシュッと通してあげる。これだけで、あなたの書くアプリケーションの堅牢性がぐっと上がります。
もしまた文字化けや謎の不一致バグに出会ったら、「あ、レゴブロックの組み立て方が違うのかもな」と今回の話を思い出してみてくださいね。
あなたのこれからのコーディングライフが、少しでも快適で楽しいものになりますように。それではまた別の記事でお会いしましょう!

コメント