JavaScriptの配列ソートを使い分ける:sort()の落とし穴と実装パターン

JavaScriptのsort()メソッドは配列を並び替える基本機能ですが、デフォルトの動作が予想外の結果を生むことがあります。この記事では、数値ソート、オブジェクト配列のソート、カスタム比較ロジックなど、実務で即座に活用できる4つのソートパターンと、よくあるエラーの回避方法を紹介します。

JavaScriptの配列ソートが複雑な理由

JavaScriptのsort()メソッドはデフォルトで配列要素を文字列として比較します。そのため、[10, 5, 40, 25].sort()[10, 25, 40, 5]という直感に反した結果になります。これが多くの開発者を困らせる主な原因です。

パターン1:数値配列の昇順・降順ソート

昇順ソート(小から大へ)

数値を正しく並び替えるには、比較関数を指定する必要があります。

const numbers = [10, 5, 40, 25, 1000];

// 正しい昇順ソート
const sorted = numbers.sort((a, b) => a - b);
console.log(sorted); // [5, 10, 25, 40, 1000]

// よくある間違い(文字列比較になる)
const wrong = numbers.sort();
console.log(wrong); // [1, 10, 25, 40, 5] ← 期待値と異なる!

降順ソート(大から小へ)

const numbers = [10, 5, 40, 25, 1000];

// 降順ソート
const descending = numbers.sort((a, b) => b - a);
console.log(descending); // [1000, 40, 25, 10, 5]

元の配列を変更しない方法

sort()は元の配列を変更します(破壊的操作)。元の配列を保持したい場合は、スプレッド演算子でコピーを作成してからソートします。

const numbers = [10, 5, 40, 25];
const original = numbers;

// 方法1:スプレッド演算子でコピーしてソート
const sorted = [...numbers].sort((a, b) => a - b);

console.log(original); // [10, 5, 40, 25] ← 変わらない
console.log(sorted);   // [5, 10, 25, 40]

// 方法2:slice()でコピーしてソート
const sorted2 = numbers.slice().sort((a, b) => a - b);

パターン2:オブジェクト配列のソート

実務ではユーザーデータや商品情報など、オブジェクト配列をソートする場面が多くあります。

特定プロパティで昇順ソート

const users = [
  { id: 1, name: '田中', age: 28 },
  { id: 2, name: '鈴木', age: 35 },
  { id: 3, name: '佐藤', age: 22 }
];

// ageプロパティで昇順ソート
const sortedByAge = [...users].sort((a, b) => a.age - b.age);
console.log(sortedByAge);
// [{ id: 3, name: '佐藤', age: 22 }, ...]

// nameプロパティで昇順ソート(文字列なのでlocaleCompareを使用)
const sortedByName = [...users].sort((a, b) => 
  a.name.localeCompare(b.name)
);
console.log(sortedByName);
// [{ id: 1, name: '田中', ... }, { id: 3, name: '佐藤', ... }, ...]

複数条件でソート

部門ごとに分類し、各部門内で給与が高い順に並び替えるような場合です。

const employees = [
  { name: '太郎', department: '営業', salary: 400000 },
  { name: '花子', department: 'IT', salary: 500000 },
  { name: '次郎', department: '営業', salary: 450000 },
  { name: '美咲', department: 'IT', salary: 480000 }
];

// まず部門でソート、次に同じ部門内で給与で降順ソート
const sorted = [...employees].sort((a, b) => {
  // 第1条件:department(昇順)
  if (a.department !== b.department) {
    return a.department.localeCompare(b.department);
  }
  // 第2条件:salary(降順)
  return b.salary - a.salary;
});

console.log(sorted);
// [{ name: '花子', department: 'IT', salary: 500000 }, ...]

パターン3:日付のソート

Date オブジェクトの比較

const events = [
  { title: '会議A', date: new Date('2025-03-15') },
  { title: '会議B', date: new Date('2025-03-10') },
  { title: '会議C', date: new Date('2025-03-20') }
];

// 日付の早い順(昇順)でソート
const sorted = [...events].sort((a, b) => a.date - b.date);
console.log(sorted);
// 2025-03-10 → 2025-03-15 → 2025-03-20 の順

// 日付の新しい順(降順)でソート
const reversed = [...events].sort((a, b) => b.date - a.date);

ISO文字列日付でソート

const logs = [
  { action: 'ログイン', timestamp: '2025-03-15T10:30:00Z' },
  { action: 'ログアウト', timestamp: '2025-03-15T09:15:00Z' },
  { action: 'ファイル作成', timestamp: '2025-03-15T10:45:00Z' }
];

// ISO文字列は辞書順比較で正しくソートできる
const sorted = [...logs].sort((a, b) => 
  a.timestamp.localeCompare(b.timestamp)
);

パターン4:カスタムソート関数の実装

複雑なロジックの再利用可能な関数化

// 汎用的なオブジェクト配列ソート関数
function sortByProperty(array, property, order = 'asc') {
  const sorted = [...array];
  return sorted.sort((a, b) => {
    const valueA = a[property];
    const valueB = b[property];
    
    // nullやundefinedの処理
    if (valueA == null) return 1;
    if (valueB == null) return -1;
    
    // 文字列の場合
    if (typeof valueA === 'string') {
      const comparison = valueA.localeCompare(valueB);
      return order === 'asc' ? comparison : -comparison;
    }
    
    // 数値の場合
    if (typeof valueA === 'number') {
      return order === 'asc' ? valueA - valueB : valueB - valueA;
    }
    
    return 0;
  });
}

// 使用例
const users = [
  { name: '山田', score: 85 },
  { name: null, score: 90 },
  { name: '鈴木', score: 78 }
];

const sorted = sortByProperty(users, 'score', 'desc');
console.log(sorted);
// [{ name: null, score: 90 }, { name: '山田', score: 85 }, ...]

ハマりやすいポイント&解決策

問題1:大文字と小文字が区別される

const words = ['apple', 'Banana', 'cherry', 'Date'];

// デフォルト比較(大文字が先になる)
console.log(words.sort()); 
// ['Banana', 'Date', 'apple', 'cherry']

// 大文字小文字を区別しないソート
const caseInsensitive = [...words].sort((a, b) => 
  a.toLowerCase().localeCompare(b.toLowerCase())
);
console.log(caseInsensitive);
// ['apple', 'Banana', 'cherry', 'Date']

問題2:日本語の順序が正しくない

const cities = ['東京', '大阪', '京都', '青森'];

// 正しい日本語ソート(localeCompareを使用)
const sorted = [...cities].sort((a, b) => a.localeCompare(b, 'ja'));
console.log(sorted);
// ['青森', '大阪', '京都', '東京']

問題3:小数点がある数値のソート

const prices = [10.5, 10.05, 10.50, 10.005];

// 単純な減算だと浮動小数点誤差が発生することがある
const sorted1 = [...prices].sort((a, b) => a - b);

// より安全な比較方法
const sorted2 = [...prices].sort((a, b) => {
  if (a < b) return -1;
  if (a > b) return 1;
  return 0;
});

console.log(sorted2); // [10.005, 10.05, 10.5, 10.50]

ソート使用時の判断基準

使うべき場面 使うべきでない場面
UIリストの表示順序 データベースクエリで大規模データ
クライアント側での小~中規模データ処理 リアルタイム処理が必要な場合
ページネーション前の結果整形 ソート結果を頻繁に変更する場合

よくある質問

はい、sort()は破壊的メソッドで、元の配列を変更します。元の配列を保持したい場合は、spread演算子(...)slice()でコピーしてからソートしてください。

JavaScriptエンジンは自動的に効率的なソートアルゴリズム(クイックソートやマージソート)を選択するため、通常はクライアント側でのソートは避け、バックエンド(SQLなど)でソートしたデータを取得することが推奨されます。どうしてもクライアント側で処理する場合は、部分的なデータのみを表示するページネーション機能の実装をご検討ください。

複数条件のソートは冗長になりやすいため、ライブラリの使用も検討できます。ただし、シンプルな2~3条件であれば&&演算子で短縮できます:const sorted = [...data].sort((a, b) => a.dept.localeCompare(b.dept) || b.salary - a.salary);

まとめ

  • JavaScriptのsort()はデフォルトで文字列比較。数値ソートには(a, b) => a - bの比較関数が必須
  • 元の配列を保持したい場合は、spread演算子またはslice()でコピーしてからソート
  • オブジェクト配列のソートには、プロパティの型に応じて減算(数値)またはlocaleCompare()(文字列)を使い分ける
  • 複数条件でのソートは、比較関数内で第1条件が同じ場合に第2条件を評価する論理を実装
  • 大規模データ
K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →