並び替えとは何か
並び替え(ソート)は「配列の要素を一定の順序で並べ直す」処理です。ここが重要です:JavaScript の Array.prototype.sort は“文字列として比較する”のが既定動作なので、数値や日付を期待どおりに並べるには“比較関数(コンパレータ)”を必ず与えます。さらに、元配列を壊さないなら“非破壊版”の toSorted(新しい仕様)を使うのが安全です。
// 破壊的(元配列を書き換える)
arr.sort((a, b) => a - b);
// 非破壊(元配列を保つ)
const sorted = arr.toSorted((a, b) => a - b);
JavaScript基本の比較関数(コンパレータ)の書き方
数値の昇順・降順
const nums = [10, 2, 33, 1];
// 昇順
nums.sort((a, b) => a - b); // [1, 2, 10, 33]
// 降順
nums.sort((a, b) => b - a); // [33, 10, 2, 1]
JavaScriptここが重要です:sort() の既定は文字列比較なので、[10, 2] をそのまま sort() すると ["10","2"] の辞書順で [10, 2] にならず、期待外れが起きます。数値は必ず a - b を使う。
文字列の昇順・降順(locale 対応)
const names = ["あ", "ア", "a", "B"];
names.sort((a, b) => a.localeCompare(b, "ja")); // 日本語ロケールで比較
// 降順
names.sort((a, b) => b.localeCompare(a, "ja"));
JavaScriptここが重要です:localeCompare は人間向けのアルファベット・ひらがな・カタカナの比較に強い。単純な < 比較より自然な並びになります。
日付の並び替え(Date / ISO文字列)
const dates = ["2024-05-01", "2023-12-31", "2025-01-01"];
dates.sort((a, b) => new Date(a) - new Date(b)); // 昇順
const objs = [{ d: new Date("2023-01-02") }, { d: new Date("2023-01-01") }];
objs.sort((a, b) => a.d - b.d);
JavaScriptここが重要です:ISO8601の文字列(YYYY-MM-DD)は文字列比較でも昇順が近いですが、時刻やタイムゾーンが絡むなら必ず Date にして数値比較に統一します。
オブジェクト配列の並び替え(キーを指定する)
単一キーで昇順・降順
const users = [
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" },
{ id: 3, name: "Carol" }
];
// id 昇順
users.sort((a, b) => a.id - b.id);
// name 昇順(日本語対応)
users.sort((a, b) => a.name.localeCompare(b.name, "ja"));
JavaScriptここが重要です:キーが文字列なら localeCompare、数値なら a.key - b.key。混在(文字列数値)なら事前に型を揃えるか Number(a.key) - Number(b.key) にする。
複数キーで並び替え(安定ソートの設計)
// 1. status("active" を先)、2. createdAt 昇順
const order = (a, b) =>
(b.status === "active") - (a.status === "active") || // active を先に
new Date(a.createdAt) - new Date(b.createdAt); // 次に日付昇順
const sorted = items.toSorted(order);
JavaScriptここが重要です:複数条件は“先の比較が同値なら次の比較へ”という形で || を使うと短く書けます。toSorted は安定ソートなので、等しいキーの元順序が保たれて予期せぬ入れ替わりが減ります。
欠損・null・undefined の扱い(安全な並び替え)
欠損を末尾へ寄せる
const bySafe = arr.toSorted((a, b) => {
const ka = a.key, kb = b.key;
if (ka == null && kb == null) return 0; // 両方欠損
if (ka == null) return 1; // a を後ろへ
if (kb == null) return -1; // b を後ろへ
return String(ka).localeCompare(String(kb), "ja");
});
JavaScriptここが重要です:null/undefined を“どこに置くか”を決めてから比較します。ka == null は null と undefined をまとめて判定できるので実務で便利。
数値キーの欠損(既定値で比較)
arr.sort((a, b) => (a.score ?? -Infinity) - (b.score ?? -Infinity));
JavaScriptここが重要です:欠損は “最小値として扱う”など、方針を決めて ?? で既定値に置き換える。仕様を明確に。
非破壊・破壊の使い分け(実務に効く選択)
元配列を壊さない toSorted を優先
const sorted = rows.toSorted((a, b) => a.id - b.id);
// rows はそのまま、sorted だけ並び替え済み
JavaScriptここが重要です:UI状態や共有配列では非破壊が安全。sort() は破壊的で、他の参照があると副作用になります。もし環境が古く toSorted が無いなら、rows.slice().sort(...) でコピーしてから並び替えます。
カスタム順序(優先度・手作りの並び)
事前定義の順序リストで並べる
const order = ["high", "medium", "low"];
const rank = new Map(order.map((k, i) => [k, i]));
const tasks = [{ p: "low" }, { p: "high" }, { p: "medium" }];
const sorted = tasks.toSorted((a, b) => (rank.get(a.p) ?? 999) - (rank.get(b.p) ?? 999));
JavaScriptここが重要です:ドメイン固有の順序(優先度、ステータス、カテゴリ)は“ランキング表”を作って比較すると簡潔で高速。
自然順ソート(数字を含む文字列)
const files = ["file2", "file10", "file1"];
files.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
// ["file1", "file2", "file10"]
JavaScriptここが重要です:{ numeric: true } は“自然順”での比較。ファイル名やバージョンっぽい文字列に向きます。
オブジェクトのキーを並び替える(表示用整形)
entries → sort → fromEntries
const obj = { b: 2, a: 1, c: 3 };
const sortedObj = Object.fromEntries(
Object.entries(obj).toSorted(([ka], [kb]) => ka.localeCompare(kb))
);
// { a: 1, b: 2, c: 3 }
JavaScriptここが重要です:オブジェクトの「キー順」は仕様により挿入順等のルールがあります。表示や出力で順序を制御したい場合は entries にして並び替え、fromEntries で戻します。
パフォーマンスと安定性(重要ポイントの深掘り)
比較関数は軽く・決定的に
比較関数は非常に多く呼ばれます。毎回重い処理(new Date を何度も生成、複雑な関数を都度計算)を避け、必要なら事前にキーを“整形・キャッシュ”します。
// キーの前計算(Schwartzian transform)
const prepared = items.map(x => ({ x, key: new Date(x.createdAt).getTime() }));
prepared.sort((a, b) => a.key - b.key);
const sorted = prepared.map(p => p.x);
JavaScriptここが重要です:比較関数の中を軽くするとソート全体が速くなります。
安定ソートの恩恵
toSorted は安定ソート(同じキーの相対順が保たれる)。複数条件ソートで副作用を避けたい、段階的ソートが必要な場面で有利です。古い環境で安定性が欲しい場合は“最初に元インデックスを持たせて比較の最後に使う”手もあります。
const withIndex = items.map((x, i) => ({ x, i }));
withIndex.sort((a, b) => cmp(a.x, b.x) || a.i - b.i);
JavaScriptよくある落とし穴と回避策
既定の sort をそのまま使う
数値や日付を並べるのに比較関数を省くと、文字列比較になって誤った順序になります。数値は a - b、日付は Date を数値化して比較。
破壊的ソートで状態を壊す
sort() は元配列を書き換えます。共有状態では toSorted または slice().sort() を選びます。
欠損や型揺れを放置
null/undefined の位置、文字列/数値の混在を事前に決めて比較に反映します。?? で既定値、Number() や String() で正規化する。
すぐ使えるレシピ(現場の定番)
数値昇順・降順(非破壊)
const asc = arr => arr.toSorted((a, b) => a - b);
const desc = arr => arr.toSorted((a, b) => b - a);
JavaScriptオブジェクト配列をキーで並び替え
const sortBy = (arr, key, dir = "asc") => {
const cmp = (a, b) => {
const va = a[key], vb = b[key];
if (va == null && vb == null) return 0;
if (va == null) return 1;
if (vb == null) return -1;
return typeof va === "number" && typeof vb === "number"
? va - vb
: String(va).localeCompare(String(vb), "ja", { numeric: true });
};
return arr.toSorted((a, b) => (dir === "asc" ? cmp(a, b) : cmp(b, a)));
};
JavaScript複数キーで並び替え(優先順位あり)
const sortByMany = (arr, ...cmps) =>
arr.toSorted((a, b) => {
for (const cmp of cmps) {
const r = cmp(a, b);
if (r !== 0) return r;
}
return 0;
});
// 使用例
const byStatus = (a, b) => (b.status === "active") - (a.status === "active");
const byDateAsc = (a, b) => new Date(a.createdAt) - new Date(b.createdAt);
const sorted = sortByMany(items, byStatus, byDateAsc);
JavaScriptまとめ
並び替えの核心は「比較関数をきちんと書く」ことです。数値は a - b、文字列は localeCompare、日付は Date 数値で比較。欠損の扱いを明確にし、複数条件は || で段階化。元配列は壊さない toSorted を優先し、性能が気になるならキーの前計算で比較を軽くする。これを押さえれば、初心者でも短く、正確で実務に耐えるソートが書けます。
