こんにちは、かつコーチです。
前回は分割代入で、オブジェクトや配列から値を取り出す方法を解説しました。
今回は、同じ ...(ドット3つ)という記号を使う「スプレッド構文」と「残余引数(レストパラメータ)」を取り上げます。
見た目は同じ ... ですが、使う場所によって役割が正反対になる面白い構文です。
スプレッド構文とは?
配列やオブジェクトを「展開」する構文
スプレッド構文とは、配列やオブジェクトの中身を「展開」して、別の配列・オブジェクト・関数の引数として使えるようにする書き方です。
... を配列やオブジェクトの前に書くことで使えます。
const numbers = [1, 2, 3];
console.log(...numbers);
// 出力: 1 2 3
...numbers と書くと、配列 [1, 2, 3] がバラバラの値 1, 2, 3 として展開されます。
なぜ必要なのか
配列やオブジェクトを「コピーする」「結合する」という処理は、これまでは concat や Object.assign といった専用のメソッドを使う必要がありました。
スプレッド構文を使うと、こうした処理を [ ] や { } の中に ... を書くだけの直感的な見た目で実現できます。
// 従来の書き方:concatで配列を結合する
const arr1 = [1, 2];
const arr2 = [3, 4];
const combinedOld = arr1.concat(arr2);
console.log(combinedOld);
// 出力: [1, 2, 3, 4]
// スプレッド構文を使った書き方
const combinedNew = [...arr1, ...arr2];
console.log(combinedNew);
// 出力: [1, 2, 3, 4]
見た目が直感的なだけでなく、配列以外の値を間に混ぜたり、複数の配列を組み合わせたりする自由度も高くなります。
基本の書き方
配列のコピー・結合
配列を丸ごとコピーしたいときにもスプレッド構文が便利です。
const original = ["赤", "青", "緑"];
// 元の配列をコピーして新しい配列を作る
const copied = [...original];
copied.push("黄");
console.log(original);
// 出力: ['赤', '青', '緑'](元の配列は変わらない)
console.log(copied);
// 出力: ['赤', '青', '緑', '黄']
... を使わずに const copied = original; としてしまうと、コピーではなく「同じ配列を指す別の名前」になってしまい、copied を変更すると original まで変わってしまいます。
この違いは配列やオブジェクトを扱う上でとても重要なポイントです。
オブジェクトのコピー・結合
オブジェクトの場合も同じ考え方で使えます。
const user = {
name: "かつコーチ",
role: "エンジニア",
};
// role だけ上書きした新しいオブジェクトを作る
const updatedUser = { ...user, role: "コーチ" };
console.log(user);
// 出力: { name: 'かつコーチ', role: 'エンジニア' }
console.log(updatedUser);
// 出力: { name: 'かつコーチ', role: 'コーチ' }
{ ...user, role: "コーチ" } のように、展開した後に同じプロパティ名を上書きすると、そのプロパティだけを変更した新しいオブジェクトを作れます。
Reactなどのフレームワークで「元のstateを直接変更せずに新しいオブジェクトを作る」という場面で、この書き方が頻繁に登場します。
関数の引数として展開する
配列をそのまま関数の引数として渡したいときにも使えます。
function calcTotal(a, b, c) {
return a + b + c;
}
const prices = [1000, 2000, 3000];
const total = calcTotal(...prices);
console.log(total);
// 出力: 6000
calcTotal(prices) ではなく calcTotal(...prices) と書くことで、配列の中身がそれぞれ別の引数として展開されます。
残余引数(レストパラメータ)とは?
「残りすべて」を配列としてまとめて受け取る
残余引数(レストパラメータ)は、スプレッド構文と同じ ... を使いますが、役割は正反対です。
こちらは関数の引数を定義する側で使い、「残りの引数をすべて配列としてまとめて受け取る」ために使います。
function showAllScores(...scores) {
console.log(scores);
}
showAllScores(80, 90, 100);
// 出力: [80, 90, 100]
引数の数が決まっていない関数を作りたいときに便利です。
先頭の引数と組み合わせて使う
残余引数は、他の引数と組み合わせて「最初の1つだけ個別に受け取り、残りはまとめて受け取る」という使い方もよくします。
function announce(topSpeaker, ...otherSpeakers) {
console.log(`本日のトップバッターは${topSpeaker}さんです。`);
console.log(`その他の登壇者:${otherSpeakers.join("、")}`);
}
announce("かつコーチ", "田中さん", "佐藤さん");
// 出力: 本日のトップバッターはかつコーチさんです。
// 出力: その他の登壇者:田中さん、佐藤さん
残余引数は必ず引数の最後に置く必要があり、これより後に別の引数を並べることはできません。
つまずきやすいポイント:スプレッド構文と残余引数の混同
同じ ... でも場所によって意味が真逆になる
私が実際につまずいたのが、この2つの違いです。
見た目が完全に同じ ... なので、コードレビューで指摘されるまで「なんとなく展開する記号」くらいの理解でごちゃまぜに使っていました。
❌ Before:役割を意識せず何となく ... を使ってしまう
function showScores(...scores) {
// scores は配列としてまとめて渡ってくる(残余引数)
console.log(scores);
}
const myScores = [80, 90, 100];
// 配列をそのまま渡してしまい、意図しない結果になる
showScores(myScores);
// 出力: [[80, 90, 100]](配列の中に配列が入ってしまう)
showScores(myScores) と書くと、scores の中には myScores という配列がそのまま1つの要素として入ってしまいます。
「残余引数で受け取っているから、呼び出す側でも ... が要るのでは」という思い込みが原因で、しばらく意図しない結果に気づけませんでした。
✅ After:呼び出す側ではスプレッド構文で展開して渡す
function showScores(...scores) {
console.log(scores);
}
const myScores = [80, 90, 100];
// スプレッド構文で配列を展開してから渡す
showScores(...myScores);
// 出力: [80, 90, 100]
整理すると、次のように役割がまったく逆になっています。
| 使う場所 | 名前 | 役割 |
|---|---|---|
| 関数を呼び出す側(引数を渡す) | スプレッド構文 | 配列やオブジェクトを展開する |
| 関数を定義する側(引数を受け取る) | 残余引数 | 複数の値を配列としてまとめる |
「渡すときは展開、受け取るときはまとめる」と覚えておくと、混同しにくくなります。
応用:分割代入と組み合わせる
残りのプロパティをまとめて取り出す
残余引数は、分割代入と組み合わせることで、「一部だけ取り出して残りはまとめる」という使い方もできます。
const user = {
name: "かつコーチ",
age: 35,
role: "エンジニア",
city: "東京都",
};
const { name, ...rest } = user;
console.log(name);
// 出力: かつコーチ
console.log(rest);
// 出力: { age: 35, role: 'エンジニア', city: '東京都' }
name だけを個別に取り出し、それ以外のプロパティは rest という1つのオブジェクトにまとめられます。
フォームの入力値から特定の項目だけ除外したい場合などに便利な書き方です。
まとめ
この記事のポイント
- スプレッド構文は配列・オブジェクトを「展開」する(呼び出す側で使う)
- 残余引数は複数の値を「配列にまとめる」(関数を定義する側で使う)
- どちらも見た目は同じ
...だが役割は正反対 - スプレッド構文で配列やオブジェクトを安全にコピー・結合できる
- 分割代入と残余引数を組み合わせると「一部を取り出して残りをまとめる」処理も書ける
次に読むべき記事
次回は「デフォルト引数の使い方」を解説します。
関数の引数が渡されなかったときの挙動を整理できる、基本文法の総仕上げとなる内容です。
→ 次の記事:デフォルト引数の使い方