残余引数とスプレッド構文をご存知でしょうか?
どちらも ...
という記号を使う JavaScript の構文です。
残余引数
function sum(...theArgs) { // 引数を配列にまとめている
let total = 0;
for (const arg of theArgs) {
total += arg;
}
return total;
}
// 参考: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters
スプレッド構文
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 配列の中身を展開している
// expected output: 6
// 参考: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax
コードを見ると残余引数は引数を圧縮しており、スプレッド構文は文字通り広げているのですが、個人的に混乱することが多かったのでそれぞれの用途 & 何が嬉しいのかについてまとめていきます。
残余引数
残余引数は数が不明な引数を配列として受け入れることができる構文です。
MDN の例を使用します。
function sum(...theArgs) {
let total = 0;
for (const arg of theArgs) {
total += arg;
}
return total;
}
console.log(sum(1, 2, 3)); // 3つの引数を使用
// expected output: 6
console.log(sum(1, 2, 3, 4)); // 4つの引数を使用
// expected output: 10
JavaScript は多く受け取りすぎた引数を無視します。そのため通常は規定の数しか引数を取れず、渡すこともできないのですが、残余引数を使うことで配列にまとめる形で柔軟に引数を取れます。
また「2つ目以降の引数が存在するか分からない」といったケースにも対応できます。あまりいい例ではありませんが以下のコードは動作します ↓
function sum(num1, ...theArgs) {
let total = num1;
for (const arg of theArgs) {
total += arg;
}
return total;
}
// console.log(sum(1, 2, 3)) -> 6
つまり残余引数は、
- 引数を配列に圧縮してくれる
- 引数がいくつになるか分からない場合でも柔軟に対応できるのが嬉しい!
という構文です。
スプレッド構文
スプレッド構文は、配列や文字列などの反復可能オブジェクト、そしてオブジェクトを展開できる構文です。
MDN の例を見ていきましょう。
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// expected output: 6
残余引数は同じように sum
という関数で使われていますが、よくよく見ると使い方が違いますね。
残余引数は関数の引数を設定するのに使われていましたが、スプレッド構文は引数として値を渡すために使われています。
numbers
は数値の入った配列です。
sum
はおそらく数値を受け入れることを想定した関数でしょう。 +
演算子を使うとエラーが出るので配列は引数に取りたくないはずです。
そこで残余引数の出番です。
残余引数が配列の中身を展開してくれるので、 sum(...numbers)
とすることで sum(1, 2, 3)
と同じ意味になります。これは便利。
ちなみにオブジェクトは関数の引数としては展開できません。
const obj = {};
console.log(sum(...obj));
// -> Uncaught TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function
まあ key: value
はオブジェクトの中に入っていないと成立しない書き方だし、単体だと意味わからないですよね。
ではオブジェクトを展開するタイミングはいつかというと、新しいオブジェクトを作成するときです。
let kotaro = {
name: "kotaro",
age: 10000,
isProgrammer: true,
}
kotaro = { ...kotaro, favoriteAnimal: ["Cat", "Penguin"]}
上の例はオブジェクトに新しいプロパティを追加しているのではなく、オブジェクトを格納している変数 kotaro
に新しく生成したオブジェクトを再代入しています。
配列でも同じことができます。
let items = ["apple", "banana", "lemon"];
items = [...items, "melon"];
こんなことをしなくても要素は追加できるじゃないか、と思うかもしれません。
スプレッド構文が真価を発揮するのは「コピー」ではないでしょうか。
const kotaro = {
name: "kotaro",
age: 10000,
isProgrammer: true,
};
const kotaro2 = { ...kotaro };
const items = ["apple", "banana", "lemon"];
const items2 = [...items];
マジで私が2人に増えればいいんですがね。 (でもダラダラしてて結局使い物にならなそう)
スプレッド構文を使用して配列 / オブジェクトを展開することで、全く新しい配列 / オブジェクトを生成することができます。つまり参照渡しではなく値渡しでのコピーとなるわけです。
const kotaro = {
name: "kotaro",
age: 10000,
isProgrammer: true,
};
const kotaro2 = { ...kotaro };
kotaro.needs = "coffee";
console.log(kotaro.needs); // -> coffee
console.log(kotaro2.needs); // -> undefined
これまた便利ですね。
つまりスプレッド構文は、
- 文字列や配列、オブジェクトなどを展開してくれる
- 文字列や配列などの反復可能オブジェクトは関数の引数として展開できる
- 配列やオブジェクトを値渡しの形でコピーすることができるので嬉しい!
という構文です。
まとめ
🗜 残余引数 🗜
- 引数を配列に圧縮してくれる
- 引数がいくつになるか分からない場合でも柔軟に対応できるのが嬉しい!
🚿 スプレッド構文 🚿
- 文字列や配列、オブジェクトなどを展開してくれる
- 文字列や配列などの反復可能オブジェクトは関数の引数として展開できる
- 配列やオブジェクトを値渡しの形でコピーすることができるので嬉しい!