配列の破壊的メソッド — sort・splice・push と非破壊の代替
この章の目次開く
配列メソッドには、元の配列をそのまま残して新しい値を返すものと、元の配列を直接変更するものがあります。 この章では、後者の破壊的メソッドをまとめます。
破壊的メソッド早見表
| メソッド | 何をする | 戻り値 | 非破壊の代替 |
|---|---|---|---|
push(...items) | 末尾に追加 | 追加後の配列の長さ | [...arr, value] / concat |
pop() | 末尾を削除 | 取り除いた要素 | slice(0, -1) |
unshift(...items) | 先頭に追加 | 追加後の配列の長さ | [value, ...arr] |
shift() | 先頭を削除 | 取り除いた要素 | slice(1) |
splice(start, deleteCount, ...items) | 途中の追加・削除・置換 | 削除した要素の配列 | toSpliced() |
sort(compareFn?) | 並び替え | 並び替えた元の配列(同じ参照) | toSorted() |
reverse() | 逆順にする | 逆順にした元の配列(同じ参照) | toReversed() |
fill(value, start?, end?) | 指定値で埋める | 埋めた元の配列(同じ参照) | コピーしてから fill |
copyWithin(target, start, end?) | 配列内で要素をコピー | 元の配列(同じ参照) | 目的に応じて slice / map |
arr[i] = value | 要素を置き換える | 代入した値そのもの | with(i, value) |
学習者const newItems = items.push('C') って書いたら、newItems が 3
になってハマりました……。追加後の配列が返ってくると思ってたのに。
破壊的メソッドでいちばん事故が多いのが、この戻り値の勘違いです。破壊的メソッドは「変更後の新しい配列」を返しません。返るのは「長さ」「取り除いた要素」「削除した要素の配列」「元の配列そのもの」のいずれかです。以降の節では、メソッドごとに引数と戻り値を明示していきます。
push / pop — 末尾に追加・削除する
push は末尾に要素を追加し、pop は末尾の要素を取り除きます。
どちらも元の配列を変更します。
構文: array.push(...items) / array.pop()
| メソッド | 引数 | 戻り値 |
|---|---|---|
push(...items) | 末尾に入れる値(複数可) | 追加後の配列の長さ(number) |
pop() | なし | 取り除いた末尾要素。空配列なら undefined |
const items = ['A', 'B'];
items.push('C');
console.log(items); // ["A", "B", "C"]
const last = items.pop();
console.log(last); // "C"
console.log(items); // ["A", "B"]元の配列を変えたくない場合は、スプレッド構文や slice を使います。
const items = ['A', 'B'];
const added = [...items, 'C'];
const removedLast = items.slice(0, -1);
console.log(items); // ["A", "B"]shift / unshift — 先頭に追加・削除する
unshift は先頭に要素を追加し、shift は先頭の要素を取り除きます。
どちらも元の配列を変更します。
構文: array.unshift(...items) / array.shift()
| メソッド | 引数 | 戻り値 |
|---|---|---|
unshift(...items) | 先頭に入れる値(複数可) | 追加後の配列の長さ |
shift() | なし | 取り除いた先頭要素。空配列なら undefined |
const queue = ['B', 'C'];
queue.unshift('A');
console.log(queue); // ["A", "B", "C"]
const first = queue.shift();
console.log(first); // "A"
console.log(queue); // ["B", "C"]shift 単体で見ると、先頭の1件を取り出して残りの要素が前に詰まります。
キューのように「来た順に1件ずつ処理する」ときは、オブジェクトの配列(レコード)でも同じです。
const tasks = [
{ id: 1, title: '設計' },
{ id: 2, title: '実装' },
{ id: 3, title: 'レビュー' },
];
const current = tasks.shift();
console.log(current); // { id: 1, title: "設計" }
console.log(tasks); // [{ id: 2, title: "実装" }, { id: 3, title: "レビュー" }]空の配列に shift すると戻り値は undefined です。ループで使うときは、先に長さを確認します。
const pending = [
{ id: 1, title: '設計' },
{ id: 2, title: '実装' },
];
while (pending.length > 0) {
const task = pending.shift();
run(task);
}非破壊で書くなら、追加は [value, ...arr]、削除は slice(1) です。
const queue = ['B', 'C'];
const added = ['A', ...queue];
const removedFirst = queue.slice(1);splice — 途中の追加・削除・置換
splice は配列の途中に対して、削除・追加・置換を行うメソッドです。
柔軟ですが、元の配列を直接変更するため注意が必要です。
構文: array.splice(start, deleteCount, ...items)
| 引数 | 渡せるもの | 説明 |
|---|---|---|
start(第1引数) | 数値 | 変更を始める位置。負の数なら末尾から数える(-1 は最後の要素) |
deleteCount(第2引数) | 数値・省略可 | start から削除する個数。省略すると start 以降を全部削除。0 なら削除なし |
...items(第3引数以降) | 任意の値・省略可 | start の位置に挿入する要素。省略すると削除だけを行う |
戻り値: 削除された要素の配列。1件だけ削除した場合も配列で返る。何も削除しなければ空配列 []
引数の組み合わせで「削除・挿入・置換」を1つのメソッドで賄うため、splice は何をしているのか読み取りにくくなりがちです。やりたいことから引くと次のようになります。
| やりたいこと | 書き方 | 戻り値 |
|---|---|---|
| 途中の2件を削除 | arr.splice(1, 2) | 削除した2件の配列 |
| 削除せず挿入だけ | arr.splice(1, 0, 'X') | [](削除0件のため) |
| 1件を別の値に置換 | arr.splice(1, 1, 'X') | 削除した1件の配列 |
| その位置以降を全部削除 | arr.splice(1) | 削除した要素の配列 |
| 末尾の1件を削除 | arr.splice(-1, 1) | 削除した1件の配列 |
コードで確認します。
const tasks = ['設計', '実装', 'レビュー'];
tasks.splice(1, 1, 'テスト'); // 1番目を1件削除して 'テスト' を挿入 = 置換
console.log(tasks); // ["設計", "テスト", "レビュー"]const items = ['A', 'B', 'C', 'D'];
console.log(items.splice(1, 0, 'X')); // [] ← 削除していないので空配列
console.log(items); // ["A", "X", "B", "C", "D"]
console.log(items.splice(2)); // ["B", "C", "D"] ← 第2引数を省略すると以降を全部削除
console.log(items); // ["A", "X"]オブジェクトの id など、条件に合う最初の要素を1件削除する場合は、findIndex で位置を探してから splice する方法を使います。
ES2023以降は、非破壊の toSpliced が使えます。
const tasks = ['設計', '実装', 'レビュー'];
const updated = tasks.toSpliced(1, 1, 'テスト');
console.log(updated); // ["設計", "テスト", "レビュー"]
console.log(tasks); // ["設計", "実装", "レビュー"]
先生splice と toSpliced は引数がまったく同じで、戻り値だけが違うと覚えると混乱しません。splice
は「取り除いたもの」を、toSpliced は「作り直した配列」を返します。
sort — 並び替える
sort は配列を並び替えますが、元の配列を直接変更します。
構文: array.sort(compareFn?)
| 引数 | 渡せるもの | 説明 |
|---|---|---|
compareFn(省略可) | (a, b) => number | 2要素の並び順を決める関数。省略すると文字列に変換して比較する |
戻り値: 並び替えたあとの配列。ただし新しい配列ではなく、元の配列と同じ参照が返る
const scores = [80, 100, 60];
const sorted = scores.sort((a, b) => a - b);
console.log(sorted); // [60, 80, 100]
console.log(scores); // [60, 80, 100] ← 元も並び替わっている
console.log(sorted === scores); // true ← コピーではなく同じ配列比較関数の戻り値の符号で並び順が決まります。ここが sort のいちばん分かりにくい部分です。
compareFn(a, b) の戻り値 | 並び順 |
|---|---|
| 負の数 | a を b より前に置く |
0 | 順序を変えない(ES2019以降は安定ソートを保証) |
| 正の数 | b を a より前に置く |
(a, b) => a - b で昇順、(a, b) => b - a で降順、と覚えておけば足ります。
比較関数を省略した sort() は、要素を文字列に変換してから比較します。数値の並び替えでは意図しない順番になるため、比較関数を必ず渡します。
[10, 9, 1].sort(); // [1, 10, 9] ← "10" < "9" として比較される
[10, 9, 1].sort((a, b) => a - b); // [1, 9, 10]文字列の並び替えでも、大文字小文字が混ざると意図とズレます。コード単位の比較では大文字がすべて先に来るためです。こうした場合は localeCompare を比較関数に使います。
['banana', 'apple', 'Cherry'].sort(); // ["Cherry", "apple", "banana"] ← 大文字が先
['banana', 'apple', 'Cherry'].sort((a, b) => a.localeCompare(b)); // ["apple", "banana", "Cherry"]元の配列を変えたくない場合は、ES2023の toSorted を使います。
const scores = [80, 100, 60];
const sorted = scores.toSorted((a, b) => a - b);
console.log(sorted); // [60, 80, 100]
console.log(scores); // [80, 100, 60]toSorted が使えない環境では、コピーしてから sort します。
const sorted = [...scores].sort((a, b) => a - b);reverse — 逆順にする
reverse は配列の順番を逆にします。
これも元の配列を直接変更します。
構文: array.reverse()(引数なし)
戻り値: 逆順にしたあとの配列。sort と同じく、元の配列と同じ参照が返る
const steps = ['入力', '確認', '完了'];
steps.reverse();
console.log(steps); // ["完了", "確認", "入力"]非破壊で書くなら toReversed を使います。
const steps = ['入力', '確認', '完了'];
const reversed = steps.toReversed();
console.log(reversed); // ["完了", "確認", "入力"]
console.log(steps); // ["入力", "確認", "完了"]with — 要素の置き換えを非破壊で行う
配列の要素を arr[i] = value で書き換えるのも、元の配列を直接変更する操作です。
const users = ['太郎', '花子', '次郎'];
users[1] = '佐藤';
console.log(users); // ["太郎", "佐藤", "次郎"]ES2023の with を使うと、指定した位置だけ置き換えた新しい配列を作れます。
構文: array.with(index, value)
| 引数 | 渡せるもの | 説明 |
|---|---|---|
index | 数値 | 置き換える位置。負の数なら末尾から数える(-1 は最後) |
value | 任意の値 | その位置に入れる新しい値 |
戻り値: 1要素だけ置き換えた新しい配列。元の配列は変わらない。範囲外の index を渡すと RangeError
const users = ['太郎', '花子', '次郎'];
const updated = users.with(1, '佐藤');
console.log(updated); // ["太郎", "佐藤", "次郎"]
console.log(users); // ["太郎", "花子", "次郎"]
console.log(users.with(-1, '田中')); // ["太郎", "花子", "田中"]
users.with(10, '山本'); // RangeError: Invalid index : 10ES2023の非破壊メソッド早見表
破壊的メソッドと引数は同じで、戻り値だけが「新しい配列」に変わるのがこの4つです。
| メソッド | 引数 | 戻り値 |
|---|---|---|
toSorted(compareFn?) | sort と同じ | 並び替えた新しい配列 |
toReversed() | なし | 逆順にした新しい配列 |
toSpliced(start, deleteCount?, ...items) | splice と同じ | 変更後の新しい配列(削除要素の配列ではない) |
with(index, value) | 位置と新しい値 | 1要素だけ置き換えた新しい配列 |
実務パターン: 状態更新では非破壊にする
Reactのstateのように、変更検知が参照の違いに依存する場面では、元配列を直接変更しない書き方が基本です。

// ❌ 元の配列を直接変更している
items.push(newItem);
setItems(items);
// ⭕ 新しい配列を作る
setItems([...items, newItem]);削除・並び替え・置き換えも同じです。
// 削除
setItems(items.filter((item) => item.id !== targetId));
// 並び替え
setItems(items.toSorted((a, b) => a.order - b.order));
// 置き換え
setItems(items.map((item) => (item.id === targetId ? { ...item, done: true } : item)));ちゃんと使うためのポイント
push/pop/shift/unshift/splice/sort/reverse/fillは元配列を変更する- 破壊的メソッドの戻り値は「変更後の新しい配列」ではない。
push/unshiftは長さ、pop/shiftは取り除いた要素、spliceは削除した要素の配列、sort/reverseは元の配列と同じ参照 splice(start, deleteCount, ...items)は第2引数を省略すると以降を全部削除し、0を渡すと挿入だけを行うsortは比較関数を渡す。戻り値が負ならaが前、正ならbが前。数値は(a, b) => a - bで昇順- ES2023では
toSorted/toReversed/toSpliced/withが非破壊の代替になり、こちらは戻り値が新しい配列になる - UIのstate更新では、元配列を直接変更せず新しい配列を作る
次は、初期化や平坦化で使う 配列の初期化・平坦化メソッド を見ていきます。
参考リンク
- MDN — Array.prototype.push
- MDN — Array.prototype.pop
- MDN — Array.prototype.shift
- MDN — Array.prototype.unshift
- MDN — Array.prototype.splice
- MDN — Array.prototype.sort
- MDN — Array.prototype.reverse
- MDN — Array.prototype.toSorted
- MDN — Array.prototype.toReversed
- MDN — Array.prototype.toSpliced
- MDN — Array.prototype.with