JavaScript 反復処理 スプレッド演算子

最終更新日

ES6で追加されたスプレッド演算子(…)は、オブジェクトや配列の要素を展開したり、まとめたりする演算子です。スプレッド演算子は、オペランドの前にドットを3つ続ける形式(…オペランド)で記述します。具体的には、スプレッド演算子を使うことで、次のような実装を行うことができます。

スプレッド演算子の使用

  • 関数実行時に、配列の要素の値を複数の引数に展開して設定する
  • 関数実行時に、複数の引数をオブジェクトや配列の要素としてまとめる
  • 配列やオブジェクトの複製(コピー)や結合を行う
スプレッド演算子を反復可能オブジェクトに適用すると、配列の複製などの挙動がイテレータの実装に従うようになります。なお、ES2018以降では、イテレータを保持しないオブジェクト(通常のオブジェクト{})に対しても、スプレッド演算子を使えるようになっています。

構文:スプレッド演算子の記法

関数宣言の使用
function 関数( ...args ) { }                                        //渡された引数を配列にまとめる
function 関数( arg1, arg2, ...args ) { }                            //arg1, arg2以外を配列にまとめる

関数実行時の使用
const params = [ arg1, arg2 ];
function 関数( arg1, arg2 ) {  }
関数( ...params );                                                  //配列を展開してパラメータとして渡す

配列の複製(コピー)に使用
const 複製された配列 = [ ...元の配列 ];

配列の作成
const 結合された配列 = [ ...配列1, …配列2 ];                          //配列を結合
const 配列 = [ "要素1", ...配列1, "要素2" ];                         //配列の任意の場所で使用可能

オブジェクトでの使用(ES2018)
const 複製されたオブジェクト = { ...元のオブジェクト };                //オブジェクトの複製
const 結合されたオブジェクト = { ...オブジェクト1, …オブジェクト2 };   //オブジェクトを結合できる
const オブジェクト = { "プロパティ1": "値1", ...元のオブジェクト, "プロパティ2": "値2" }; //任意の位置に挿入できる

関数宣言時の使用

関数宣言時の引数に使われるスプレッド演算子は、引数をまとめて1つの配列として保持する役割があります。これは、可変長引数などを扱うための強引な手法です。可変長引数とは、引数の数があらかじめ決まっていない引数のことです。一方、仮引数の数があらかじめ固定の長さで定義されている引数を固定長引数と呼びます。

3つの値を加算

function sum( val1, val2, val3 ){
    return val1 + val2 +val3;
}

このように固定長で引数を設定した場合、引数の数のパターン分だけ関数を定義する必要がある。

このようなケースでスプレッド演算子を使うと、可変長引数を扱うことができます。

構文:可変長引数の記法

function ( [ arg1, arg2,  ] ...args ){ }
  • arg1、arg2:任意の固定長引数を設定できます。このとき、arg1、arg2には、1番目と2番目に渡された実引数の値がそれぞれ渡されます。arg1、arg2に渡された実引数は、argsの配列に含まれません。
  • args:arg1、arg2で受け取れなかった残りの引数が配列となってargsに渡されます。スプレット演算子によって処理されるか可変長引数は、残余引数とも言います。

argsの中身

function fn( ...args ){
    console.log( args );
}
fn( 1, "こんにちは", false );
>[ 1, "こんにちは", false ]

残余引数で引数の合計値を算出する関数を作成

function sum( ...vals ){            //引数は配列(vals)の要素として格納される
    let returnValue = 0;
    for( const val of vals ){
        returnValue += val;       //配列の要素を1つずつreturnValueに加算する
    }
return returnValue;
}
console.log( sum(1,2) );
>3
console.log( sum(1,2,3) );
>6
console.log( sum(1,2,3,4) );
>10

関数実行時の使用

関数の引数として渡す配列にスプレット演算子を付与すると、配列の中身が展開されて引数として関数に渡せます。

Math.maxで与えられた引数の最大値を確認

const maxValue = Math.max( 10, 5, 18, 9 );  //Math.maxは0個以上の引数(数値)のうち最大の数を返す関数。引数を1つずつ渡す
console.log( maxValue );
>18

このように、Math.maxにいは、比較対象となる値を引数として1つずつ渡す必要があります。しかし、比較対象となるデータは、しばしば配列のような形式で保持されているため、配列の要素を1つずつ渡すのは非常に面倒です。そのような場合にスプレット演算子を使うと、次のように記述できます。

関数実行時のスプレット演算子の利用

const numArray = [ 10, 5, 18, 9 ];
const maxValue = Math.max( ...numArray );  ///スプレット演算子によって配列の要素が引数として渡される
console.log( maxValue );
>18

このようにすれば、わざわざ配列のインデックスを指定して個別に引数を渡す必要がなくなります。

配列の複製に使用

配列の複製にも、スプレット演算子を使うことができます。スプレット演算子を使って配列を複製した場合には、配列の1階層目の値が、新しい配列の要素として複製(コピー)されることになります。そのため、複製した配列の値を変更しても、元の配列に影響しません。

配列の複製

const original = [ "元の値" ];
const duplicated = [ ...original ];          //元の配列の要素を持つ新しい配列を作成
duplicated[ 0 ] = "変更後の値";               //複製後の配列値を変更
console.log( `original[ ${ original[ 0 ] } ] duplicated[ ${ duplicated[ 0 ] } ]` );
>original[ 元の値 ] duplicated[ 変更後の値 ] //元の配列の値は変更されていない

これは次のような、再代入の処理とは、明確に区別して使用する必要があります。

配列を異なる変数に再代入

const original = [ "元の値" ];
const notduplicated = original;              //※1 originalの参照をnotDuplicatedに再代入
notduplicated[ 0 ] = "変更後の値";            //値を変更
console.log( `original[ ${ original[ 0 ] } ] notduplicated[ ${ notduplicated[ 0 ] } ]` );
>original[ 変更後の値 ] notduplicated[ 変更後の値 ] //元の配列の値は変更される

※1のように配列(original)を他の変数(notDuplicated)に再代入した場合には、配列への参照がコピーされるだけで配列自身が複製されるわけではありません。スプレット演算子による配列の複製は、配列のconcatメソッドやsliceメソッドによる複製と同じ挙動を取ります。

配列の作成

スプレット演算子を使うと、配列をマージ(結合)をマージ(結合)したり、任意の位置に要素を追加した配列を簡単に作成したりできます。

配列の作成

const arry1 = [ 10, 20, 30 ];
const arry2 = [ 40, 50, 60 ];
console.log( [...arry1, ...arry2 ] );   //配列のマージ
> [10, 20, 30, 40, 50, 60]

console.log( [...arry2, ...arry1 ] );   //配列のマージ
> [40, 50, 60, 10, 20, 30]

console.log( [0, ...arry2, 70,  ...arry1 ] );   //任意の位置に要素を追加
> [0, 40, 50, 60, 70, 10, 20, 30]

オブジェクトでの使用

スプレット演算子は、基本的に反復可能オブジェクトで使いますが、ES2018以降ではオブジェクトに対しても使えるようになっています。

スプレット演算子を使ってオブジェクトを作成

const obj1 = { prop1: 10, prop2: 20 };
const obj2 = { prop3: 30, prop4: 40 };

console.log( {...obj1, ...obj2 } );                  //オブジェクトのマージ
>{ prop1:10,  prop2:20,   prop3:30,   prop4:40}

console.log( { prop0: 0, ...obj2, prop5: 50 } );     //任意のプロパティの追加
>{ prop0:0, prop3:30,  prop4:40,   prop5:50}

console.log( { prop1: 0, ...obj1 } );                //プロパティ重複時は、あとから定義したほうで上書き
>{ prop1: 10, prop2: 20}

なお、関数実行時の引数を、オブジェクトとスプレット演算子を使って記述することはできません。

関数実行時にはオブジェクトとスプレット演算子で引数を渡すことはできない

const obj = { prop1: 10, prop2: 20 };
Math.max( ...obj );
>Uncaught TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function
//型に関するエラー:呼び出し可能なイテレータが見つかりません。

反復可能オブジェクトをスプレット演算子で複製した場合には、イテレータの挙動に従ってスプレット演算子は動作します。そのため、オブジェクトのSymbol.iteratorプロパティの処理を変更すると、その挙動が変化します。

Symbol.iteratorの変更後にスプレット演算子で配列を複製

Array.prototype[ Symbol.iterator ] = function* (){
    yield "Hello";
    yield "World";
}
const arry = [1, 2, 3];
const newArry = [...arry];
console.log( newArry );
>[ "Hello", "World" ]