スプレッド構文(Spread Operator) ...
で使用
for of
で回せるArray-like(イテラブル?)なオブジェクトを個々の値で展開できる
※配列での仕様はES2015/ES6で標準になっているが、オブジェクトでの仕様はまだドラフト (ECMAScript® 2022 Language Specification)
apply
を利用して配列を展開して関数に渡して居たような処理をスプレッド構文でシンプルに書くことができます。
function sum(x, y, z) { return x + y + z } const numbers = [1, 2, 3] console.log( sum(...numbers) ) // 6 console.log( sum.apply(null, numbers) ) // 6
関数の引数をスプレッド構文すれば、関数に渡される引数(arguments)を配列として扱うことができます。(argumentsとは違い配列なのでそのままmap
,reduce
などの配列操作をすることができる)
function sum(...values) { // valuesは配列 return values.reduce((sum, val) => { return sum + val }, 0) } console.log( sum(1, 2, 3) ) // 6
配列
配列のコピー
[...Array]
で元の配列をコピーした新しい配列を作成
const arg1 = [1, 2, 3] // 配列のコピー const arg2 = [...arg1] // [1, 2, 3] // コピーなので元の配列とは別 console.log(arg1 === arg2) // false arg2[0] = 0 console.log(arg1, arg2) // [1, 2, 3] [0, 2, 3] // 要素を追加した新しい配列を作成 const arg3 = ['a', ...arg1, 'b'] // ['a', 1, 2, 3, 'b']
配列の結合
concat()
で結合していたのが簡単に書ける
const arg1 = [0, 1, 2] const arg2 = [3, 4, 5] var arg3 = [...arg1, ...arg2] // [0, 1, 2, 3, 4, 5] // ↓ と同等 arg3 = arg1.concat(arg2) // [0, 1, 2, 3, 4, 5]
配列に要素を追加するpush()
, unshift()
の引数としても使える
var arg1 = [1, 2, 3] var arg2 = ['a', 'b', 'c'] arg1.push(...arg2) // [ 0, 1, 2, 'a', 'b', 'c' ]
var arg1 = [1, 2, 3] var arg2 = ['a', 'b', 'c'] arr1.unshift(...arr2) // [ 'a', 'b', 'c', 0, 1, 2 ]
オブジェクト
オブジェクトのコピー
{...Object}
で新しいオブジェクトを作成
const obj1 = {a: 1, b: 2} // オブジェクトのコピー const obj2 = {...obj1} // { a: 1, b: 2 } console.log(obj2 === obj1) // false obj2.a = 0 console.log(obj1, obj2) // { a: 1, b: 2 } { a: 0, b: 2 } // 要素を追加した新しいオブジェクトを作成 const arg3 = {z: 0, ...obj1, c: 3} // { z: 0, a: 1, b: 2, c: 3 }
オブジェクトのマージ
配列と同じ様に{...obj, ...obj}
でオブジェクトをマージすることができます。
const obj1 = {a: 1, b: 2} const obj2 = {c: 3, d: 4} const mergedObj = {...obj1, ...obj2} // { a: 1, b: 2, c: 3, d: 4 }
オブジェクトに同じキーがある場合、最後に追加された値が残ります。
obj = { name: 'Mika', name: 'Aki', name: 'Mikko' } console.log(obj) // { name: 'Mikko' }
同じキーが有るオブジェクトのマージ
const obj1 = {a: 1, b: 2} const obj2 = {a: 3, c: 4} const mergedObj = {a: 0, ...obj1, ...obj2, c: 5} // { a: 3, b: 2, c: 5 }
スプレッド構文の注意点
オブジェクト中に配列は展開できるが、配列にオブジェクトは展開できない
オブジェクトを[...obj]
と配列中にコピーしようとするとエラーになります
const obj = {a:1, b:2} const arg = [1, ...obj, 2] // TypeError: obj is not iterable
逆に配列を{...array}
でオブジェクト中にコピーすると、配列のインデックスをキーとしてオブジェクトにコピーされます。
const arg = ['foo', 'bar'] const obj1 = {a: 1, ...arg, b: 2} // { '0': 'foo', '1': 'bar', a: 1, b: 2 } // 配列のインデックスと同じキーが存在している場合は、マージされる const obj2 = {0: 'a', ...arg, 2: 'b'} // { '0': 'foo', '1': 'bar', '2': 'b' }
浅いコピー(shallow copy)
スプレッド演算子...
でコピーした配列・オブジェクトは浅いコピー(shallow copy)なので、ネストされている配列・オブジェクトでは注意が必要
配列
const arg = [1, [2, 3], 4] var copy = [...arg] copy[1][0] = 'a' console.log(arg) // [ 1, [ 'a', 3 ], 4 ]
オブジェクト
const obj = { a: { b: 1 }, c: 2 } var copy = {...obj} copy.a.b = 0 copy.a.d = 3 console.log(obj) // { a: { b: 0, d: 3 }, c: 2 }
ネストされている配列・オブジェクトを操作するともとの配列に影響を及ぼしてしまう
null, undefined
iterableではないnull
,undefined
をスプレッド演算子で扱おうとした場合、配列([...null]
, [...undefined]
)はエラーになるが、オブジェクト({...null}
, {...undefined}
)だとエラーにならない
[...null] // TypeError: null is not iterable [...undefined] // TypeError: undefined is not iterable {...null} // {} {...undefined} // {}
スプレッド構文の活用
文字列を配列にする
文字列(String)はArray-likeなオブジェクトなので...str
で1文字づつ配列にすることができる。
const str = 'こんにちわJavaScript🎌' console.log([...str]) // ['こ','ん','に','ち','わ','J','a','v','a','S','c','r','i','p','t','🎌' ] const reversed = [...str].reverse().join('') // 🎌tpircSavaJわちにんこ
絵文字のようなものも1文字として扱われるっぽい。
配列・オブジェクトの分割
分割代入(Destructuring assignment) 構文は、配列から値を取り出して、あるいはオブジェクトからプロパティを取り出して別個の変数に代入することを可能にする JavaScript の式です。
出典: 分割代入 - JavaScript | MDN
配列
var [a, b, ...rest] = [1, 2, 3, 4, 5, 6, 7] console.log(a, b, rest) // 1 2 [ 3, 4, 5, 6, 7 ]
スプレッド構文は"残り"でしか指定できない
var [a, b, ...rest, c] = [1, 2, 3, 4, 5, 6, 7] // => SyntaxError: Rest element must be last element
オブジェクト
オブジェクトの分割代入は変数名と同じキーの値が取得される
var {a, b} = {a:10, b:20, c:30} console.log(a, b) // 10 20 var {c, d} = {foo:10, bar:20, baz:30} console.log(c, d) // undefined undefined
スプレッド構文で分割代入されたものはkey:valueを保持したオブジェクト形式になる
var {a, c, ...rest} = {a:1, b:2, c:3, d:4, e:5} console.log(a, c, rest) // 1 3 { b: 2, d: 4, e: 5 }
↓みたいに関数の引数で...
を使用しているのも分割代入なのかな?
function myFunc(x, y, ...other) { ... }
配列の重複を削除
一意な値を格納できるSet
を利用すると配列から重複したデータを取り除いた配列を作成することができます。
const data = [0, 1, 2, 3.1, true, false, 1, "2", "", 3.1, null, undefined, NaN, false, NaN] const dist = [...new Set(data)] // [ 0, 1, 2, 3.1, true, false, '2', '', null, undefined, NaN ]
new Set()
がSetオブジェクトを返したのを[]
で配列化しているという認識で良いのでしょうか?
オブジェクトの初期値
{...null}
, {...undefined}
が{}
になることを利用するとオブジェクトのデフォルトオプションをつくったりできる
const createObj = (options) => { return {name: 'アシリパ', ...options} } obj1 = createObj() // {name: 'アシリパ'} obj2 = createObj({name: '不死身の杉元'}) // {name: '不死身の杉元'}
ハックっぽいので見通しが良いかどうかは、ちょっと考えどころかもしれない
イテレーターがちゃんと理解できてないのでイテラブルとArray-likeの違いとかがイマイチまだあやふやなままです...
[参考]
- スプレッド構文 - JavaScript | MDN
- JSのスプレッド構文を理解する - Qiita
- 【JavaScript】スプレッド構文の便利な使い方まとめ - Qiita
- JavaScript の イテレータ を極める! - Qiita
- 分割代入 - JavaScript | MDN
- Set - JavaScript | MDN