【実務・中級編】 String.prototype.localeCompare()による文字列比較 – JavaScript実践ガイド

おい、最近コード書いてるか?

フロントエンドをある程度触ってくると、避けて通れないのが「データの並び替え(ソート)」や「文字列の比較」だ。ユーザーが入力したフォームのバリデーションだったり、テーブルヘッダーをクリックして昇順・降順を切り替えたりする機能は、どんなWebアプリでも必ず実装する。

その時、お前らは文字列の比較に何を使っている?
「そんなの簡単だよ、`>` や `<` 演算子を使えばいいだろ」と思ったそこの君。ちょっと待ってくれ。実はそれ、日本語や特殊文字を扱う現場では地雷原を裸足で歩いているようなものだ。

今回は、JavaScriptの文字列比較の隠しボス、`String.prototype.localeCompare()` について、実務で本当に役立つ知見を叩き込んでやろう。マニュアルには書いていない、ブラウザの裏側の挙動や泥臭いハックまで解説するから、最後までついてきな。

—

なぜ普通の比較演算子(`>` / `

JavaScriptの `<` や `>` 演算子は、純粋な「Unicodeコードポイントの大小」で比較している。要するに、文字の裏側にある「文字コードの番号」を比べているだけだ。

これが英語圏のシンプルなアルファベット(`’a’` と `’b’`)なら問題ない。だが、日本語やアクセント記号付きの欧州言語(`é` と `e` など)が混ざった瞬間、この素朴な比較は崩壊する。

例えば、ユーザーの氏名リストをソートするとき、文字コード順で並べられたらどうなる?
「濁音や半濁音、漢字の読み順が完全に無視され、システムとして使い物にならないゴミクズのようなソート結果」が画面にレンダリングされることになる。QAチームから「これ、並び順おかしくないですか?」とチケットが飛んできて、青ざめるお前の姿が目に浮かぶようだ。

ここで登場するのが、ロケール(地域や言語)のルールを考慮して文字列を比較してくれる `localeCompare()` だ。

—

`localeCompare()` の基本仕様と返り値の秘密

まずは基本のおさらいをしておこう。`localeCompare()` は、基準となる文字列が、引数で渡した文字列に対して「前にあるか、後ろにあるか、あるいは同じか」を数値で返すメソッドだ。

// 基本的な使い方
const result = “あ”.localeCompare(“い”);
console.log(result); // 負の値(例: -1 または -2 など。環境による)

返り値のルールは以下の通り。頭に叩き込んでおけ。

  • 負の値(通常は `-1`): 基準の文字列が、引数の文字列よりも前にくる場合(ソートで先に来る)
  • 正の値(通常は `1`): 基準の文字列が、引数の文字列よりも後にくる場合(ソートで後に来る)
  • `0`: 両者が等しい場合

`Array.prototype.sort()` のコールバック関数にそのままブチ込めるように設計されているのが、このメソッドの最高にイケているところだ。

const fruits = [“りんご”, “バナナ”, “みかん”, “アボカド”];

// localeCompareを使うだけで、日本語の自然な辞書順ソートができる
fruits.sort((a, b) => a.localeCompare(b, “ja”));

console.log(fruits);
// 出力: [ ‘アボカド’, ‘りんご’, ‘バナナ’, ‘みかん’ ] (※実際の辞書順の厳密さは環境によるが、少なくとも文字コード順の惨事からは逃れられる)

—

ブラウザの裏側で何が起きているか?(V8エンジンとIntl APIの裏話)

ここで少し、シニアとして知っておくべき「ブラウザの裏側の話」をしておこう。

JavaScriptのエンジン(ChromeのV8など)で `localeCompare()` が呼ばれたとき、内部では何が起きているのか?
実は、JavaScriptのコアが独自に比較アルゴリズムを持っているわけではない。裏側では、ECMAScriptの国際化APIである `Intl.Collator` が使われている。

ブラウザは、OSや内蔵している ICU(International Components for Unicode)ライブラリを呼び出して、その言語圏特有の複雑なソート規則(例えば、ドイツ語のウムラウト `ä` をどう扱うか、日本語のひらがなとカタカナの優先度など)を計算している。

ここで注意が必要なのは、「実行する環境(ブラウザやNode.jsのバージョン)によって、微妙にソート結果が変わる可能性がある」という点だ。特にモバイル端末の古いWebViewや、軽量なNode.jsイメージ(Alpine Linuxなどで行儀の悪いビルドをしたもの)では、ICUデータが削られていてロケールが正しく機能しないバグに遭遇したことが、俺のキャリアでも何度かある。
サーバーサイドレンダリング(SSR)や、厳密な整合性が求められるデータを扱うときは、この「環境依存性」を常に頭の片隅に置いておくんだぞ。

—

実務で使える!最強のオプション活用術

さて、ここからが本番だ。`localeCompare()` の真骨頂は、第2引数の `locales` と、第3引数の `options` オブジェクトを使いこなしたときにある。

現場でよくある「イケてないコード」と「プロのコード」を比較してみよう。

1. 大文字・小文字を無視した比較・ソート

通常、`’a’` と `’A’` を比べると、文字コードの関係で大文字が先に来たりして厄介だ。UIの検索機能やユーザー名のソートでは、大文字小文字を区別したくないことが多い。

そんなときは `sensitivity: ‘base’` オブジェクトを渡せ。

// 大文字・小文字、あるいはアクセント記号の違いを無視して比較したい時
const str1 = “apple”;
const str2 = “APPLE”;

// 普通に比較すると別物扱い
console.log(str1.localeCompare(str2)); // 0ではない

// sensitivity: ‘base’ を指定すると、文字の種類(アルファベットとしての基本形)だけで比較する
console.log(str1.localeCompare(str2, “en”, { sensitivity: “base” }));
// 出力: 0 (同じとみなされる!)

2. 人間にとって自然な数値ソート(Numeric Sorting)

これ、フロントエンド開発者なら全員一度はハマる地獄だ。「ファイル1」「ファイル2」「ファイル10」というファイル名をソートしたとき、通常の文字列比較だとどうなるか?

`[“file1”, “file10”, “file2”]`

……そう、`10` が `2` の前にきやがる。人間が見たら発狂するバグだよね。
これを解決するために、わざわざ正規表現で数値を切り出してパースしていなかったか? そんな無駄な労力はもう必要ない。`numeric: true` を使え。

const files = [“file10.txt”, “file2.txt”, “file1.txt”];

// numeric: true を指定するだけで、人間が期待する数値順にソートしてくれる
files.sort((a, b) => a.localeCompare(b, “en”, { numeric: true }));

console.log(files);
// 出力: [ ‘file1.txt’, ‘file2.txt’, ‘file10.txt’ ]

うおっ、まじか!って思っただろ? これを知っているだけで、くだらないユーティリティ関数を自作する手間がごっそり消える。

—

現場のパフォーマンスに関する注意点

最後に、パフォーマンスの話をしておこう。

`localeCompare()` は、裏側で重厚長大なICUのロジックを回しているため、プレーンな `>` や `<` 演算子と比べると圧倒的に実行速度が遅い。
数万件の巨大な配列に対して、何も考えずに `sort()` の中で毎回 `localeCompare()` を呼び出すと、メインスレッドがブロックされてUIがカクつく原因になる。

もしパフォーマンスがクリティカルな大量データのソートで、かつ言語の厳密なルールがそこまでシビアに問われないのであれば、`Intl.Collator` を事前にインスタンス化して使い回すか、あるいは比較の回数を減らす工夫(Schwartzian transform など)を検討すべきだ。

とはいえ、一般的なUIコンポーネントで扱う数十〜数百度のリストであれば、気にせず `localeCompare()` を使って問題ない。コードのメンテナンス性と正確性を優先しよう。

—

まとめ

  • 文字列の比較やソートに `>` や `<` を使うな。日本語や特殊文字で必ず痛い目を見る。
  • 基本は `str1.localeCompare(str2, ‘ja’)` を使う。
  • 大文字小文字を無視したいなら `{ sensitivity: ‘base’ }` を活用しろ。
  • 「file1」「file10」問題には `{ numeric: true }` が特効薬。
  • ただし、パフォーマンスの過信は禁物。数万件規模のソートではベンチマークを取れ。

現場で文字列周りのバグを踏んだら、まずはこのメソッドのオプションを思い出すんだな。お前らのコードが、より堅牢で洗練されることを期待している。それじゃ、また次の現場で!

コメント

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