JavaScript | 配列・オブジェクト:実務パターン – 並び替え

JavaScript JavaScript
スポンサーリンク

並び替えとは何か

並び替え(ソート)は「配列の要素を一定の順序で並べ直す」処理です。ここが重要です: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 == nullnullundefined をまとめて判定できるので実務で便利。

数値キーの欠損(既定値で比較)

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 を優先し、性能が気になるならキーの前計算で比較を軽くする。これを押さえれば、初心者でも短く、正確で実務に耐えるソートが書けます。

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