変数構文

【JavaScript】 コード中の「…」は意味があった スプレッド/レスト構文

更新日:2023/02/22

JavaScriptは本当に不思議ちゃんな言語である。
コード中で「...」とか何か考え込んでいたりする。

そうじゃなかった。
「...」には意味があった。

スプレッド構文またはレスト構文という名前の構文でした。

今回はJavaScriptのスプレッド構文とレスト構文についてお伝えします。

2023/2/20 apply()についての記述を削除しました。
2023/2/21 一部のコードを見直しました

 

スプレッド構文とレスト構文

「...」は、オブジェクトを展開、または変数やリテラルを羅列したものを一つにまとめてくれる演算子です。

使用する目的によってスプレッドとレストの二つの構文に分かれます。

スプレッド構文

スプレッド構文は配列などのオブジェクトを展開します。

レスト構文(レストパラメーター)

レスト構文は、変数やリテラルの羅列を一つに集約します。

構文名適用前形式適用後形式
スプレッド構文[1 , 2, "aaa", "bbb"]― 展開 →1 , 2, "aaa", "bbb"
レスト構文1 , 2, "aaa", "bbb"― 集約 →[1 , 2, "aaa", "bbb"]

■構文使用例

スプレッド構文とレスト構文のイメージを把握してもらうために、この記事で紹介しているコードを最初に挙げておきます。

スプレッド構文

関数呼び出し

function func( x , y , z ) { /* 処理 */ }
const array = [ 1 , 2 , 3];
func( ...array );

配列の複製

const array = [ 1 , 2 , 3 ];
const clonedArray = [ ...array ];

多次元配列の複製

const arrayClone = array => array.forEach(
        (e,index)=>Array.isArray( e ) 
            ? array[index] = arrayClone([ ...e ]) : null
    ) ? array : array;
const array = [ 1 , [ 2 , 3 ] ];
const clonedArray = arrayClone( [...array ] ); 

配列の結合・合成

const array1 = [ 1 , 2 , 3 ];
const array2 = [ 4 , 5 , 6 ];
const concatArray1 = [ ...array1 , ...array2 ];
const concatArray2 = [ ...array1 , "text1" , 100 , ...array2 ];

オブジェクトを展開

const obj1 = { x:1 , y:2 };
const obj2 = { z:3 };
const concatObj = { ...obj1 , ...obj2 }; // Object { x: 1, y: 2, z: 3 }

分割代入で使用

const array = [ 1 , 2 ];
const [x , y ,z] = [ ...array , 3 ];  // ← [ 1 , 2 , 3]
console.log( x , y , z );   //  1 2 3

イテラブルなオブジェクトで使用

const iterableObj = {
    x:1 ,
    y:2 ,
    [Symbol.iterator] :  function* (){
            yield this.x;
            yield this.y;
        }
};
const array1 = [ ...iterableObj ];

レスト構文

関数呼び出しで使用

function func(  ...param ){
    return param.length;
}
console.log( func(  1 , 2 , 3 ) ); // 3
console.log( func(  1 , 2 , 3 , 4 , 5 ) ); // 5

分割代入で使用

const [x , ...y ] = [ 1 , 2, 3 ];
console.log(x,y);   //  1 , [ 2 ,3 ]

次から少し詳しく解説するので、ここではイメージだけ抑えておいてください。

 

スプレッド構文

スプレッド構文は配列などのオブジェクトを展開します。

[1 , 2, "aaa", "bbb"]― 展開 →1 , 2, "aaa", "bbb"

まずは簡単な使い方を見ていきましょう。

関数呼び出し

スプレッド構文は関数呼び出しで利用できます。

スプレッド構文:関数呼び出し

function func( x , y , z ) {
        console.log( x , y , z );  // 結果: 1 2 3
}
const array = [ 1 , 2 , 3];
func( ...array );

上のコードは、関数func()の引数に『...』を付けて配列arrayを指定しています。
これにより、次のように配列arrayが展開されます。

func( ...[ 1 , 2 , 3] ) → func( 1 , 2 , 3 )

この結果、func()の引数xyzに、配列arrayの要素が順番に割り当てられます。

もしスプレッド演算子を使用しないなら、次のように記述するでしょう。

スプレッド構文を使用しない その1

func( array[0] , array[1] , array[2] );

スプレッド構文を知っていると、とても迂遠に感じますね。

配列に展開/配列の複製

配列の初期化にもスプレッド構文を使用できます。

配列の初期化にスプレッド構文を使用

const array = [ 1 , 2 , 3 ];
const clonedArray = [ ...array ]; //  clonedArray: Array(3) [ 1 , 2 , 3 ]

2行目の[ ...array ]は、次のように展開されて新しい配列が生成されます。

[ ...array ] → [ ...[1 , 2 , 3] ] → [ 1 , 2 , 3 ]

このコードにより、配列clonedArrayは配列arrayと同じ要素を持ちます。
つまり、配列を複製したということです。

配列を手軽に複製できるというのは、大きなメリットです。

多次元配列の展開/複製

多次元配列は完全には複製できません。

次のコードは、多次元配列が複製されたように見えます。

多次元配列にスプレッド構文を使用

const array = [ 1 , [ 2 , 3 ]  ];
const clonedArray = [ ...array ];
console.log( array );  //   Array [ 1 , [ 2 , 3] ] 
console.log( clonedArray );  //   Array [ 1 , [ 2 , 3] ] ← array と同じ

しかし、2次元配列に値を代入すると他方の配列に影響を与えます。

多次元配列にスプレッド構文を使用

array[0] = 10;
array[1][0] = 20;
console.log( array );  //  Array [ 10 , [ 20 , 3] ]
console.log( clonedArray );  //  Array [ 1 , [ 20 , 3] ] ← [1][0] が aの代入で変化した!

この結果から、二つの配列の2番目の要素は同じものを共有していることがわかります。
これでは複製と言えません。

2番目の要素は配列なので、2次元配列については複製できていないということです。

多次元配列をスプレッド構文で複製するには、次のように配列の中身に対してもスプレッド構文を適用します。

多次元配列をスプレッド構文で完全コピー

const arrayClone = array => array.forEach(
        (e,index)=>Array.isArray( e ) 
            ? array[index] = arrayClone([ ...e ]) : null
    ) ? array : array;

const array = [ 1 , [ 2 , 3 ] ];
const clonedArray = arrayClone( [...a ] ); // 配列を複製する 配列内の配列要素は複製できていない!

arrayClone()関数は、少し無理して一文にしています。
forEach()は戻り値がundefindeですが、3項演算子で配列を返すように工夫しています。

forEach()のコールバック関数内では、要素が配列のときスプレッド構文で複製して、その結果を再帰呼び出ししています。

次のコードはNull合体演算子に置き換えることができます。
array.forEach( ) ? array : array;

array.forEach( ) ?? array
今回はどんな結果でも同じ値を返すという意味で、3項演算子を使用しています。

上のコードで多次元配列を複製できているか、結果をテストしてみます。

c[ 0 ] = 10;
c[ 1 ][ 0 ] = 20;
console.log( a );   // Array [ 1 , [ 2 , 3 ] ]
console.log( c );   // Array [ 10 , [ 20 , 3 ] ]

うまく複製できました。

実際に多次元配列の複製関数を作成するとしたら、引数でスプレッド構文を使用させる仕様は不適当ですね。
そこで、引数に生のままの配列を指定するバージョンを挙げておきます。

const arrayClone2 = array => array.reduce(
        (resultArray,e)=>resultArray.push( Array.isArray( e ) 
            ?  arrayClone2(e) : e ) ? resultArray : resultArray
    ,[]);

const array = [ 1 , [ 2 , 3 ] ];
const clonedArray = arrayClone2( array ); 

配列の結合・合成

スプレッド構文は配列に何度でも記述できます。

この特性を活かして、配列の結合したり合成したりできます。

次のコードは、2つの配列と文字列を一つの配列にまとめています。

配列の結合にスプレッド構文を使用

const array1 = [ 1 , 2 , 3 ];
const array2 = [ 4 , 5 , 6 ];
const concatArray1 = [ ...array1 , ...array2 ]; // // Array(6) [ 1, 2, 3, 4, 5, 6 ]
const concatArray2 = [ ...array1 , "text1" , 100 , ...array2 ]; // Array(8) [ 1, 2, 3, "text1", 100 , 4, 5, 6 ]

concat()などの配列を結合するメソッドがありますが、スプレッド構文の方が直感的でわりやすいですね。

オブジェクトを展開

スプレッド構文は、オブジェクトのプロパティ展開もできます。

オブジェクトの展開にスプレッド構文を使用

const obj1 = { x:1 , y:2 };
const obj2 = { z:3 };
const concatenatedObj = { ...obj1 , ...obj2 }; // Object { x: 1, y: 2, z: 3 }

配列と同じように、結合もできていますね。
ただし同じキーがあると、後から出てきた値で上書きされます。

同名プロパティは上書き

const obj1 = { x:1 , y:2 };
const obj2 = { y:3 }; // ← obj1のyと重複
const concatenatedObj = { ...obj1 , ...obj2 }; // Object { x: 1, y: 3 }

結合後のプロパティyは、後方に記述したobj2のプロパティ値がセットされているのがわかりますね。

分割代入で使用

分割代入にもスプレッド構文を使用できます。

分割代入でスプレッド構文を使用

const array = [ 1 , 2 ];
const [x , y ,z] = [ ...array , 3];  // ← [ 1 , 2 , 3]
console.log( x , y , z );   //  1 2 3

後で解説するレスト構文を併用すると、配列を組み替えることができておもしろいです。

分割代入でスプレッド構文とレスト構文を使用

const a = [ 1 , 2 ];
const [x , ...b ] = [ ...a , 3 ];  // ← [ 1 , 2 , 3]
                                  // ...bはレスト構文
console.log( x , b );    //  1  array [ 2 , 3 ]

参考:【JavaScript】 分割代入はどこが便利なのか

 

スプレッド構文とイテラブルなオブジェクト

ここまで主に配列に対してスプレッド構文を使用する例を、お伝えしてきました。

実際にはスプレッド構文は、イテラブルなオブジェクトで使用します。
オブジェクトの展開は除きます)

例えば、次のように数値をスプレッド構文の対象にしてみます。

const value = 1;
const arra = [ ...value ];

すると、次のようなエラーが出力されます。

『TypeError: value is not iterable』

値がイテラブルでないと、怒られてしまったのです。

イテラブルなオブジェクトとは値を反復して返すことができるオブジェクトで、配列もその一つです。
参考:【JavaScript】 Iterator(イテレーター)とは?避けて通りたいけど説明してみる

つまり配列でなくても、次のように自作のオブジェクトをイテラブルなオブジェクトとして構築することで、スプレッド構文を利用できます。

イテラブルなオブジェクトとスプレッド構文

const iterableObj = {
    x:1 ,
    y:2 ,
    [Symbol.iterator] :  function* (){
            yield this.x;
            yield this.y;
        }
};
const array = [ ...iterableObj ]; // iterableObj.[Symbol.iterator]から値を順番に取り出す

console.log( array );  // Array [ 1 , 2 ]

 

レスト構文

レスト構文(レストパラメータ)は残余引数とも呼ばれていて、変数やリテラルの羅列を一つの配列に集約します。

1 , 2, "aaa", "bbb"― 集約 →[1 , 2, "aaa", "bbb"]

関数呼び出しで使用

関数に引き渡された引数を、一つの配列にまとめます。

次のような、可変長引数を実現するのに便利ですね。

レスト構文

function func(  ...param ){

    return param.length;

}
console.log( func(  1 , 2 , 3 ) );
console.log( func(  1 , 2 , 3 , 4 , 5 ) );

分割代入で使用

分割代入にもレスト構文を使用できます。

次のコードは、配列[ 1 , 2, 3 ]のインデックス0を変数xに、インデックス12を配列に集約して変数yに代入しています。

分割代入でレスト構文を使用

const [x , ...y ] = [ 1 , 2, 3 ];
console.log(x,y);   //  1 , [ 2 ,3 ]

参考:【JavaScript】 分割代入はどこが便利なのか

 

まとめ

配列が絡む処理で、スプレッドとレストの構文は便利に使えそうですね。

僕もこれからは積極的に使っていこうと思います。

ただ問題なのは、使用する位置によって構文の名前が変化することです。
最近記憶力が乏しくなってきたので、名前が覚えられません。

記事に書くときは毎回確認しているので、めんどうなのです。

更新日:2023/02/22

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。