スプレッド構文と残余引数および分割代入は、ECMAScript 2015(ES6)で変数を扱う新たな構文です。もっとも、構文というものは書き方だけ説明されても、具体的にどう使うのか何が便利なのかわかりにくいことが少なくありません。本稿では、具体的に思いついたコード例をいくつかご紹介します。
なお、網羅的な構文解説ではありません。各項にMDNのリファレンスへのリンクが加えてありますので、詳しくはそちらをご覧ください。
スプレッド構文
スプレッド構文...
は、配列要素をカンマ(,
)区切りの関数の引数や配列リテラルの要素の組として扱います。
const arrayRandom = [3, 1, 4, 1, 5, 9, 2, 6];
const maxInt = Math.max(...arrayRandom);
console.log('max', maxInt); // max 9
const array0 = [0, 1, 2];
const array1 = [3, 4, 5];
array0.push(...array1);
console.log(array0); // [0, 1, 2, 3, 4, 5]
もとの配列の中身は変えずに、連結した新たな配列を得たいときには、これまでもArray.prototype.concat()
メソッドが使えました。けれど、スプレッド構文...
を用いるともっと簡単です。
const array2 = [...array0, ...array1];
console.log(array2); // [0, 1, 2, 3, 4, 5]
配列のコピーをつくりたいときにも使えます。ただし、要素がオブジェクトの場合は参照が与えられ、もとの配列と共有することになりますのでご注意ください。
const array3 = [...array0];
配列のようなオブジェクトを配列にして扱う
配列のようなオブジェクトというのは、具体的には配列型(array-like)あるいは反復可能(iterable)オブジェクトです。こうしたオブジェクトから新しいArrayインスタンスをつくるには、ECMAScript 2015に備わった静的メソッドArray.from()
が使えます(「ECMAScript 6のArrayに関わる構文を試す」「Array.from()メソッド」参照)。
console.log(Array.from('文字列も反復可能'));
// ["文", "字", "列", "も", "反", "復", "可", "能"]
けれど、配列リテラル[]
とスプレッド構文...
を使えばさらに手間が減ります。
console.log([...'どうしてなんだよおおぉお!!!😫'].join('゛'));
// ど゛う゛し゛て゛な゛ん゛だ゛よ゛お゛お゛ぉ゛お゛!゛!゛!゛😫
たとえば、Document.querySelectorAll()
メソッドは、配列のようなNodeList
オブジェクトを返します。角かっこ[]
に添えたインデックスで要素を取り出したり、NodeList.length
プロパティで長さが得られます。けれど、Array
クラス独自のメソッドは使えません。
<div id="buttons">
<button type="button">button 1</button>
<button type="button">button 2</button>
<button type="button">button 3</button>
</div>
<script>
const buttons = document.querySelectorAll('#buttons button');
console.log(buttons.length, buttons[0], buttons.push);
// 3 <button type="button">button 1</button> undefined
</script>
もっとも、メソッドがまるでないわけではありません。たとえば、NodeList.prototype.forEach()
メソッドは備わっています。つぎのJavaScriptコードを加えると、ボタンをクリックしたときコンソールにそのインデックス番号が示されます。
buttons.forEach((button, id) => {
button.addEventListener('click', (event) => {
console.log(id);
});
});
さらにArray
クラスのメソッドも必要になったら、配列リテラル[]
とスプレッド構文...
を使えばよいでしょう。コードをつぎのように書き替えると、すべてのボタンをクリックしたときコンソールにcompletedと示されます。なお、ECMAScript 5.1に加わったArray.prototype.every()
メソッドは、すべての要素のコールバックがtrue
を返したときtrue
と評価します。
const buttons = [...document.querySelectorAll('#buttons button')];
buttons.forEach((button, id) => {
button.addEventListener('click', (event) => {
button.clicked = true;
const allDone = buttons.every((button) => button.clicked === true);
console.log(id);
if (allDone) {
console.log('completed');
}
});
});
残余引数
スプレッド構文と同じ...
が用いられる残余引数は逆に、カンマ(,
)区切りで渡された値の組を配列として受け取ります。
これまでも関数本体の中で変数arguments
は使えました。けれど、[]
にインデックスで要素を取り出し、arguments.length
で長さを得られるほかはプロパティも備えていません。
残余引数は配列ですので、Array
クラスのプロパティやメソッドがすべて使えます。つぎのJavaScriptコードに定めた関数は、任意の数の引数の平均値を返します。なお、ECMAScript 5.1から備わったArray.prototype.reduce()
メソッドは、要素をコールバック関数で集計して結果の値が返されます。
function getAverage(...args) {
// console.log(arguments.reduce);
// console.log(args.reduce);
return args.reduce((previous, current) =>
previous + current
) / args.length;
}
const average = getAverage(3, 1, 4, 1, 5);
console.log('average', average); // average 2.8
残余引数は名前のとおり、通常のカンマ(,
)区切りの引数のあとに定めれば、残りの引数を配列として受け取ります。つぎのJavaScriptコードの関数は、長方形か三角形あるいは円の面積を第1引数の指定に応じて計算します。なお、ECMAScript 2016に加えられたべき乗演算子**
は数値を累乗します。
function getArea(type, ...args) {
switch (type) {
case 'rectangle':
return args[1] * args[2];
case 'triangle':
return args[1] * args[2] / 2;
case 'circle':
return args[1] ** 2 * Math.PI;
}
}
console.log(getArea('rectangle', 20, 30)); // 600
console.log(getArea('triangle', 20, 30)); // 300
console.log(getArea('circle', 10)); // 314.1592653589793
分割代入
分割代入は、変数を配列リテラルのかたちで宣言することにより、配列から対応する要素が代入できる構文です。
const [a, b, c] = [0, 1, 2];
console.log(a, b, c); // 0 1 2
変数値が簡単に入れ替えられます。
let a = 0;
let b = 1;
[a, b] = [b, a];
console.log(a, b); // 1 0
分割代入とスプレッド構文あるいは残余引数を組み合わせる
分割代入はスプレッド構文や残余引数と組み合わせて使うこともできます。つぎのJavaScriptコードからは、もとの配列の中身は変えずに、Array.prototype.shift()
メソッドと同じ処理結果が得られます。
const array = [0, 1, 2, 3, 4, 5];
const [head, ...tail] = array;
console.log('head', head, 'tail', tail);
// head 0 tail [1, 2, 3, 4, 5]
前掲の図形の面積を求める関数にも、分割代入がつぎのように使えます。
function getArea(type, ...args) {
let width, height, radius;
switch (type) {
case 'rectangle':
[width, height] = args;
return width * height;
case 'triangle':
[base, height] = args;
return base * height / 2;
case 'circle':
[radius] = args;
return radius ** 2 * Math.PI;
}
}