JavaScriptの配列ソートを自在に操る:数値・文字列・オブジェクトの実践パターン

JavaScriptの配列ソートはsort()メソッドで実装しますが、デフォルト動作は文字列比較のため、数値や複雑なデータでは思い通りに並ばないことが多くあります。本記事では、実務でよく使う3つのソートパターンと、ハマりやすい落とし穴を解決する方法を解説します。

JavaScriptのsort()メソッドの基本動作

JavaScriptのsort()メソッドは、デフォルトでは配列要素を文字列に変換してUnicode順で並び替えます。このため、数値を直接ソートすると予期しない結果になります。

// ❌ 間違った例:数値が正しくソートされない
const numbers = [10, 5, 40, 25, 1000, 1];
console.log(numbers.sort());
// 出力: [1, 10, 1000, 25, 40, 5](文字列として比較されている)

// ✅ 正しい例:比較関数を指定
const numbers = [10, 5, 40, 25, 1000, 1];
console.log(numbers.sort((a, b) => a - b));
// 出力: [1, 5, 10, 25, 40, 1000]

なぜデフォルトが文字列比較なのか

JavaScriptのsort()は、すべての要素を文字列に変換してから比較する仕様になっています。これは言語の歴史的経緯ですが、実務では必ず比較関数を明示的に指定する必要があります。

数値配列のソート:昇順・降順の実装

最も基本的で頻出なパターンが数値のソートです。比較関数の戻り値を理解することが重要です。

昇順ソート(小さい順)

const prices = [2500, 1200, 5000, 800, 3500];

// 昇順ソート
const ascending = prices.sort((a, b) => a - b);
console.log(ascending);
// 出力: [800, 1200, 2500, 3500, 5000]

// 元の配列を変更したくない場合
const ascendingCopy = [...prices].sort((a, b) => a - b);
console.log(prices); // 元の配列は変わらない
console.log(ascendingCopy); // [800, 1200, 2500, 3500, 5000]

降順ソート(大きい順)

const prices = [2500, 1200, 5000, 800, 3500];

// 降順ソート
const descending = prices.sort((a, b) => b - a);
console.log(descending);
// 出力: [5000, 3500, 2500, 1200, 800]

重要な注意点:sort()は元の配列を変更する

sort()メソッドは破壊的メソッドです。元の配列を直接変更します。元の配列を保持する必要がある場合は、スプレッド演算子(...)やスライスで新しい配列を作成してからソートしてください。

文字列配列のソート:アルファベット順と日本語対応

アルファベット順のソート

const fruits = ['banana', 'apple', 'cherry', 'date'];

// 昇順(A→Z)
const ascending = fruits.sort();
console.log(ascending);
// 出力: ['apple', 'banana', 'cherry', 'date']

// 降順(Z→A)
const descending = fruits.sort().reverse();
console.log(descending);
// 出力: ['date', 'cherry', 'banana', 'apple']

大文字小文字を区別しないソート

const words = ['Apple', 'banana', 'Cherry', 'date'];

// 大文字小文字を無視したソート
const caseInsensitive = words.sort((a, b) => 
  a.toLowerCase().localeCompare(b.toLowerCase())
);
console.log(caseInsensitive);
// 出力: ['Apple', 'banana', 'Cherry', 'date']

日本語対応のソート

const cities = ['東京', '大阪', 'あきた', 'ニューヨーク'];

// localeCompare()を使用:日本語を含む正確なソート
const sorted = cities.sort((a, b) => 
  a.localeCompare(b, 'ja-JP')
);
console.log(sorted);
// 出力: ['あきた', '大阪', 'ニューヨーク', '東京']

オブジェクト配列の複合ソート

実務では、複数のプロパティを持つオブジェクトの配列をソートする場面が多くあります。

単一プロパティでのソート

const employees = [
  { name: '田中太郎', age: 35, salary: 4500000 },
  { name: '佐藤花子', age: 28, salary: 3200000 },
  { name: '鈴木次郎', age: 42, salary: 5800000 }
];

// 年齢で昇順ソート
const byAge = employees.sort((a, b) => a.age - b.age);
console.log(byAge);
// 出力: 佐藤花子(28) → 田中太郎(35) → 鈴木次郎(42)

// 給与で降順ソート
const bySalary = employees.sort((a, b) => b.salary - a.salary);
console.log(bySalary);
// 出力: 鈴木次郎(5800000) → 田中太郎(4500000) → 佐藤花子(3200000)

複数プロパティでのソート(多条件ソート)

const products = [
  { category: '飲料', name: 'コーヒー', price: 300 },
  { category: '飲料', name: 'お茶', price: 200 },
  { category: 'お菓子', name: 'クッキー', price: 500 },
  { category: 'お菓子', name: 'チョコレート', price: 400 }
];

// カテゴリで昇順、同じカテゴリ内では価格で昇順
const sorted = products.sort((a, b) => {
  if (a.category !== b.category) {
    return a.category.localeCompare(b.category, 'ja-JP');
  }
  return a.price - b.price;
});

console.log(sorted);
// お菓子 → チョコレート(400) → クッキー(500)
// 飲料 → お茶(200) → コーヒー(300)

よくあるハマりポイントと解決策

ハマりポイント1:元の配列が変更されてしまう

元の配列の変更を避けたい場合は、スプレッド演算子やスライスメソッドで新しい配列を作成してからソートしてください。

const original = [30, 10, 20];

// ❌ 間違い:元の配列が変更される
original.sort((a, b) => a - b);
console.log(original); // [10, 20, 30](元が変わった!)

// ✅ 正しい:新しい配列を作成してからソート
const sorted = [...original].sort((a, b) => a - b);
console.log(original); // [30, 10, 20](元は変わらない)
console.log(sorted);   // [10, 20, 30]

ハマりポイント2:浮動小数点数の比較

JavaScriptの浮動小数点数比較は精度問題を起こします。数値が非常に近い場合、ソート順序が不安定になる可能性があります。

const decimals = [1.1, 1.10001, 1.09999, 1.2];

// 直接比較すると精度問題が発生することがある
const sorted = decimals.sort((a, b) => a - b);
console.log(sorted); // 通常は [1.09999, 1.1, 1.10001, 1.2]

// 誤差範囲を考慮する必要がある場合
const epsilon = 0.0001;
const withTolerance = decimals.sort((a, b) => {
  const diff = a - b;
  if (Math.abs(diff) < epsilon) return 0;
  return diff;
});
console.log(withTolerance); // より安定したソート

ハマりポイント3:大規模配列のパフォーマンス

10,000要素以上の大規模な配列をソートする場合、比較関数の処理が重いと著しくパフォーマンスが低下します。複雑な文字列処理やAPI呼び出しは避け、事前に計算結果をキャッシュするなどの工夫が必要です。

sort()を使うべき場面と使うべきでない場面

使うべき場面

  • UIで表示する前に一度だけソートする場合
  • ユーザーがソート順序を変更する場面(複数回のソート)
  • 数件から数千件程度のデータセット
  • スマートフォンなどの環境で軽量な処理が必要

使うべきでない場面

  • 100万件以上の超大規模データセット(→データベースレベルでのソート推奨)
  • リアルタイムでの頻繁なソート(→ツリー構造など別のデータ構造を検討)
  • 複雑なビジネスロジック(→専用のソートライブラリ使用を検討)

類似手段:sort()以外のソート方法

sort()の代わりに、TypeScriptの型安全性を活用したい場合は、ライブラリの利用も検討できます。たとえば、Lodashの_.sortBy()_.orderBy()は、複数条件のソートをより簡潔に記述できます。ただし、シンプルなソートには標準のsort()で十分です。

// Lodashを使った場合(ライブラリが必要)
import _ from 'lodash';

const employees = [
  { name: '田中', dept: '営業', age: 35 },
  { name: '佐藤', dept: '営業', age: 28 },
  { name: '鈴木', dept: 'IT', age: 42 }
];

// 複数の条件でソート
const sorted = _.orderBy(employees, ['dept', 'age'], ['asc', 'desc']);

// 標準JavaScriptでも同様に実装可能だが、Lodashのほうが簡潔
const standardSort = employees.sort((a, b) => {
  if (a.dept !== b.dept) {
    return a.dept.localeCompare(b.dept);
  }
  return b.age - a.age;
});

実践例:Eコマースの商品一覧ソート

実際のプロジェクトで使えるコード例を紹介します。

class ProductSorter {
  constructor(products) {
    this.products = products;
  }

  // 価格で昇順ソート
  sortByPriceAsc() {
    return [...this.products].sort((a, b) => a.price - b.price);
  }

  // 評価が高い順
  sortByRating() {
    return [...this.products].sort((a, b) => b.rating - a.rating);
  }

  // 人気順(販売数で降順)
  sortByPopularity() {
    return [...this.products].sort((a, b) => b.salesCount - a.salesCount);
  }

  // 複合ソート:カテゴリ別にして、各カテゴリ内では価格順
  sortByCategory() {
    return [...this.products].sort((a, b) => {
      if (a.category !== b.category) {
        return a.category.localeCompare(b.category, 'ja-JP');
      }
      return a.price - b.price;
    });
  }
}

// 使用例
const products = [
  { id: 1, name: 'ノートPC', category: '電子機器', price: 120000, rating: 4.5, salesCount: 250 },
  { id: 2, name: 'マウス', category: '周辺機器', price: 2000, rating: 4.2, salesCount: 1200 },
  { id: 3, name: 'キ
    
K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →