JavaScriptのソートは Array.prototype.sort() で行いますが、使い方を間違えると数値が正しく並ばないという有名な落とし穴があります。この記事では、その落とし穴を押さえながら、配列・オブジェクト・HTMLテーブルのソートを実装します。
(a, b) => a - b、文字列は (a, b) => a.localeCompare(b)。引数なしの sort() は文字列として比較するため、[1, 10, 2].sort() は数値順になりません。また sort() は元の配列を変更するので、残したいときは toSorted() を使います。配列のソートと「sortの落とし穴」
まず基本の数値ソートです。比較関数 (a, b) => a - b を必ず渡します。結果が負ならaが前、正ならbが前に並びます。
const numbers = [3, 1, 10, 2]; numbers.sort((a, b) => a - b); console.log(numbers); // [1, 2, 3, 10]
sort() は要素を文字列に変換して比較します。そのため数値でも辞書順になり、意図しない並びになります。[1, 10, 2].sort() → [1, 10, 2](”1″ < “10” < “2” の順)。数値を並べるときは必ず (a, b) => a - b を渡してください。文字列は localeCompare() を使います。これも引数なしの sort() だと大文字小文字や日本語で崩れます。
const fruits = ["banana", "apple", "cherry"]; fruits.sort((a, b) => a.localeCompare(b)); console.log(fruits); // ["apple", "banana", "cherry"] // 落とし穴: 引数なしだと大文字が先に来る(UTF-16コード順) console.log(["Banana", "apple", "Cherry"].sort()); // ["Banana", "Cherry", "apple"] ← 大文字 B,C が小文字 a より前 // localeCompare なら ["apple", "Banana", "Cherry"] と自然な順になる
配列操作全般(map・filter など)は配列のmap・filter・reduceの使い方、条件で要素を絞る方法は配列から条件に合う要素を取り出す方法を参考にしてください。
オブジェクト配列のソート
オブジェクトの配列は、ソート基準となるプロパティを比較関数で指定します。
const people = [
{ name: "John", age: 25 },
{ name: "Jane", age: 30 },
{ name: "Tom", age: 20 },
];
// 数値プロパティ(年齢の昇順)
people.sort((a, b) => a.age - b.age);
// 文字列プロパティ(名前順)
people.sort((a, b) => a.name.localeCompare(b.name));
複数の基準で並べたいときは、|| で比較をつなげます。前の比較が 0(同値)のときだけ次の基準で比較されます。
// 名前順 → 同じ名前なら年齢の昇順 people.sort((a, b) => a.name.localeCompare(b.name) || a.age - b.age);
昇順・降順と「非破壊」ソート
降順にするには、比較関数の a と b を入れ替えます。また sort() は元の配列を書き換える(破壊的)点に注意します。
const nums = [3, 1, 2]; // 降順: a と b を逆にする console.log([...nums].sort((a, b) => b - a)); // [3, 2, 1] // sort() は元の配列を変更してしまう nums.sort((a, b) => a - b); console.log(nums); // [1, 2, 3] ← 元が変わっている // 元を残したいなら toSorted()(ES2023)か、コピーしてからソート const sorted = nums.toSorted((a, b) => b - a); // 古い環境では: const sorted = [...nums].sort((a, b) => b - a);
HTMLテーブルのソート(昇順↔降順トグル)
テーブルは、ヘッダーのクリックで行を並べ替えます。同じ列を再クリックしたら昇順・降順を反転させると使いやすくなります。セル値は innerText ではなくtextContentで取得します(余計なリフローを避けられます)。
<table id="myTable">
<thead>
<tr><th>名前</th><th>年齢</th></tr>
</thead>
<tbody>
<tr><td>John</td><td>25</td></tr>
<tr><td>Jane</td><td>30</td></tr>
<tr><td>Tom</td><td>20</td></tr>
</tbody>
</table>
const table = document.getElementById("myTable");
let sortState = { col: -1, asc: true };
// ヘッダーにクリックイベントを設定
table.querySelectorAll("thead th").forEach((th, index) => {
th.addEventListener("click", () => sortTable(index));
});
function sortTable(colIndex) {
const tbody = table.tBodies[0];
const rows = Array.from(tbody.rows);
// 同じ列の再クリックで方向を反転
sortState.asc = sortState.col === colIndex ? !sortState.asc : true;
sortState.col = colIndex;
const dir = sortState.asc ? 1 : -1;
rows.sort((rowA, rowB) => {
const a = rowA.cells[colIndex].textContent.trim();
const b = rowB.cells[colIndex].textContent.trim();
// 両方が数値なら数値比較、それ以外は文字列比較
const numA = Number(a), numB = Number(b);
const bothNumbers = a !== "" && b !== "" && !isNaN(numA) && !isNaN(numB);
const result = bothNumbers ? numA - numB : a.localeCompare(b);
return result * dir;
});
rows.forEach((row) => tbody.appendChild(row)); // 並び替えた順で再配置
}
どの列をどの方向でソート中かを示す矢印(▲▼)は、ヘッダーにクラスを付け外しして表現できます。クラス操作はclassListの使い方が参考になります。
よくある質問(FAQ)
sort() に比較関数を渡します。数値昇順なら (a, b) => a - b、文字列なら (a, b) => a.localeCompare(b) です。DOMテーブルは、行を配列化してソートし、並び替えた順で再配置します。sort() は要素を文字列に変換して辞書順で並べるためです。[1, 10, 2].sort() は [1, 10, 2] になります。数値は必ず (a, b) => a - b を渡してください。1(昇順)か -1(降順)を掛けることで方向を制御できます。|| でつなぎます。前の比較が 0(同値)のとき次のキーで比較されます。例:(a, b) => a.name.localeCompare(b.name) || a.age - b.ageまとめ
JavaScriptのソートは、必ず比較関数を渡すのが鉄則です。数値は (a, b) => a - b、文字列は localeCompare。引数なしの sort() は文字列順になり、[1, 10, 2] のような数値が正しく並ばない点に注意してください。
降順は a と b を入れ替え、複数キーは || で連結します。元の配列を残したいときは toSorted() を使いましょう。テーブルソートは、再クリックで昇順↔降順を反転させると使いやすくなります。

