sort()やreverse()は新しい配列を返さず元の配列を書き換える
Array.prototype.sortやreverseは呼び出した配列自体を並べ替える破壊的(ミューテーティング)メソッドです。戻り値だけを見て「新しい配列が作られた」と考えると、元のデータまで書き換わっていることに気づけません。
なぜエラーが出ないのか
出力: 1
(エラーなし)- JavaScriptは何も報告しません。文法として正しいためです
出力: 1- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 function displaySorted(scores) { 2 return scores .sort((a, b) => a - b); ^ 3 } 4 const original = [3, 1, 2]; 5 const sorted = displaySorted(original); 6 console.log(original[0]);
sort()は呼び出した配列自体を並べ替える破壊的メソッドです。scoresをそのまま渡すと呼び出し元のoriginalまで並び変わってしまいます。
直し方: scores を [...scores] にします。
パターン2
1 function topScore(scores) { 2 return scores .sort((a, b) => b - a)[0]; ^ 3 } 4 const original = [10, 30, 20]; 5 topScore(original); 6 console.log(original[0]);
sort()がscores(=original)を直接書き換えるため、関数を呼んだだけでoriginalの並び順まで変わってしまいます。
直し方: scores を [...scores] にします。
パターン3
1 function displayReversed(items) { 2 return items .reverse(); ^ 3 } 4 const original = ["a", "b", "c"]; 5 displayReversed(original); 6 console.log(original[0]);
reverse()もsort()と同じく破壊的メソッドです。itemsをそのまま渡すとoriginalの順序まで反転してしまいます。
直し方: items を [...items] にします。
パターン4
1 function sortedCopy(names) { 2 return names .sort(); ^ 3 } 4 const original = ["c", "a", "b"]; 5 sortedCopy(original); 6 console.log(original[0]);
sort()は呼び出した配列自体を並べ替えます。namesをそのまま渡すとoriginalの順序まで変わってしまいます。
直し方: names を [...names] にします。
パターン5
1 function withoutFirst(list) { 2 list .shift(); ^ 3 return list; 4 } 5 const original = [1, 2, 3]; 6 withoutFirst(original); 7 console.log(original.length);
shift()も配列自体を書き換える破壊的メソッドです。コピーせずに渡すとoriginalの要素数まで減ってしまいます。
直し方: list を [...list] にします。
よくある誤解
「関数に渡した配列は関数の外では変わらないはず」という思い込みは、破壊的メソッドを使う関数には当てはまりません。元のデータを保ちたい場合は、スプレッド構文などで複製してから並べ替える必要があります。
実務での勘所
ES2023では、この問題に対処するため、元の配列を変更せず新しい配列を返す非破壊的な仲間のメソッドが追加されました。sortにはtoSorted、reverseにはtoReversed、spliceにはtoSplicedというように、頭にtoを付けた新しいメソッドを使えば、元のデータを保ったまま並べ替えた結果だけを受け取れます。スプレッド構文で複製してから従来のsortを呼ぶ、という一手間がこれらの新メソッドで不要になりました。