JavaScriptを使っていると「…」という表記を見かけることがあります。
これはスプレッド構文というもので、配列の処理をするのに非常に便利なものです。
ここでは、スプレッド構文の使い方を実例で解説しています。
また、スプレッド構文を使ったときに表示されるエラー「error: Unexpected token ‘…’」の対処方法についてもまとめています。
スプレッド構造とは何か?
スプレッド構文とは、配列やオブジェクトの前に「…」をつけることで、配列のカッコ [ ]とオブジェクトのカッコ{}を外す処理です。
非破壊的な処理なので、元の変数のデータを壊すことなく処理を加えた新しいデータを作成することができます。
スプレッド構文の使い方
スプレッド構文の主な使い方は2つあります。
配列・オブジェクト同士を結合する
配列同士を結合する場合は以下のようになります。
[ ...配列A, ...配列B,,, ]
オブジェクト同士を結合する場合は以下のようになります。
{ ...オブジェクトA, ...オブジェクトB,,, }
配列・オブジェクトの中に新しい要素を追加する
スプレッド構文を使うと、配列・オブジェクトの中に新しい要素を追加することができます。
追加したい場所に、新しい要素を記述します。
[ ...配列, 新しい要素,,, ]
オブジェクト同士を結合する場合は以下のようになります。
{ ...オブジェクト, 新しい要素,,, }
スプレッド構文の注意点
スプレッド構文をつかう際には大きく3つの注意点があります。
全体を[]や{}で囲む必要がある
スプレッド構文を使った場合、[ ]や{}で全体を囲む必要があります。
console.logで出力する場合は[ ]や{}がなくてもエラーになりませんが、変数に代入したり、処理として実行しようとすると、「Uncaught SyntaxError: Unexpected token ‘…’」というエラーが発生します。
実例:オブジェクトの場合
obj = {a: 1, b: 2}
obj1 = ...obj
//処理結果
Uncaught SyntaxError: Unexpected token '…'
↓ { }で囲む。
obj = {a: 1, b: 2}
obj1 = {...obj}
console.log(obj1)
//処理結果
{a: 1, b: 2}
実例:配列の場合
arr = [1, 2]
brr = ...arr
//処理結果
Uncaught SyntaxError: Unexpected token '…'
↓ { }で囲む。
arr = [1, 2]
brr = [ ...arr ]
console.log(brr)
//処理結果
[1, 2]
スプレッド構文で外れるのは一番外側のカッコだけ
スプレッド構文で外れるのは一番外側のカッコだけです。入れ子になった配列の[ ]やオブジェクトは展開しません。
実例
arr = [ '000', [111, 222, [ 333, 444 ]], { a: 1 }]
console.log(...arr)
//出力
000 [111, 222, [333, 444], {a: 1}
スプレッド構文はシャローコピー
スプレッド構文で作成したデータはシャローコピーです。このため深い階層の配列やオブジェクトのデータは元のデータと共有されています。
深い階層の配列やオブジェクトを編集すると、元のデータも変わってしまうので、スプレッド構文で他の変数に代入してコピーする場合は注意が必要です。
実例
例えば、以下のように[1, [2, 3] ]という配列の中に配列が入った2次元配列「arr」という変数があるとします。
arr = [1, [2, 3] ]
これをスプレッド構文で開いて、新しい変数「brr」に代入します。
brr = [ ...arr ]
console.log(brr)
//出力
[1, [2, 3] ]
arrもbrrも値は同じです。では変数自体が同じかどうかというとそうではありません。
arr == brr
//出力
false
「arr == brr」として変数どうしを比較すると、違うもの「false」として表示されます。
このときに、「arr」と「brr」の入れ子になった配列[2, 3]の部分を比較すると以下のようになります。
arr[1] == brr[1]
//出力
true
「true」すなわち、同じものとして表示されます。
試しに、「arr」の中の入れ子になった要素[2, 3]に新しい要素として、「4」を追加する処理を加えます。
arr[1].push(4)
console.log(arr)
//出力
[1, [2, 3, 4] ]
「brr」の中身も見てみます。
console.log(brr)
//出力
[1, [2, 3, 4] ]
すると、「arr」に対して処理を加えただけなのに、「brr」まで書き換わっています。
これがシャローコピーです。外側(第1階層)は全く別物としてコピーするけど、その中の入れ子になった配列は元のやつをそのまま使います。
実例: 配列同士を結合する
スプレッド構文を使うと、一番外側のカッコを外します。
a = [1,2]
console.log(...a)
//1 2
この特性を利用して、カッコを外した複数の配列を、カッコで囲むと新たな配列を作ることができます。
a = [1,2]
b = [3,4]
console.log( [ ...a, ...b ] )
//[1,2,3,4]
なお、スプレッド構文を使用せずに配列どうしをつなげても、要素はくっつきません。
a = [1,2]
b = [3,4]
console.log(a+b)
//1,23,4
実例: オブジェクトどうしを結合する
スプレッド構文を使うことで、以下のように、obj1とobj2を結合することができます。
obj1 = { a:1, b:2 }
obj2 = { c:3, d:4 }
obj3 = { ...obj1, ...obj2 }
console.log( obj3 )
//出力
{a: 1, b: 2, c: 3, d: 4}
なお、スプレッド構文を使用せずに配列どうしをつなげても、要素はくっつきません。
obj1 = { a:1, b:2 }
obj2 = { c:3, d:4 }
console.log( obj1 + obj2 )
//出力
[object Object][object Object]
実例:配列の中に新しい要素を追加する
スプレッド構文では配列どうしをつなげる以外にも、配列の中に新しい要素を追加することができます。
追加する要素は記述した場所に追加されます。
後ろに新しい要素を追加する場合
arr = [1, 2, 3, 4]
str = "aaa"
console.log( [ ...arr, str ] )
// [1, 2, 3, 4, 'aaa']
前に新しい要素を追加する場合
arr = [1, 2, 3, 4]
str = "aaa"
console.log( [ str, ...arr ] )
// ['aaa', 1, 2, 3, 4]
配列と配列の間に新しい要素を追加する場合
arr = [1,2]
brr = [3,4]
str = "aaa"
console.log( [ ...arr, str, ...brr ] )
// [1, 2, 'aaa', 3, 4]
配列を要素として追加する
スプレッド構文を使わずに配列の入った要素を指定すれば、配列のまま入ります。(多次元配列を生成します。)
arr = [1,2]
brr = [3,4]
str = "aaa"
console.log( [ ...arr, str, brr ] )
// [1, 2, 'aaa', [3, 4]]
改行して記述する
なお、スプレッド構文を使って要素をつなげる場合に、要素ごとに改行した書き方をしている場合もあります。
ただ改行しているだけなので、焦らず落ち着いて行きましょう。
arr = [1,2]
brr = [3,4]
str = "aaa"
newArr = [
...arr,
str,
brr,
123
]
console.log( newArr )
// [1, 2, 'aaa', [3, 4], 123]
↑↓ 同じ
arr = [1,2]
brr = [3,4]
str = "aaa"
newArr = [ ...arr, str, brr, 123 ]
console.log( newArr )
// [1, 2, 'aaa', [3, 4], 123]
実例:オブジェクトの中に新しい要素を追加する
スプレッド構文ではオブジェクトどうしをつなげる以外にも、オブジェクトの中に新しい要素を追加することができます。
追加する要素は記述した場所に追加されます。
プロパティ名を指定して要素を追加する
obj1 = { a:1, b:2 }
str = "aaa"
console.log( { ...obj1, c:str } )
// {a: 1, b: 2, c: 'aaa'}
プロパティ名を指定しない場合
プロパティ名を指定しない場合は、変数名がプロパティ名になります。
obj1 = { a:1, b:2 }
str = "aaa"
console.log( { ...obj1, str } )
// {a: 1, b: 2, str: 'aaa'}
obj1 = { a:1, b:2 }
arr = ["x", "y", "z"]
console.log( { arr, ...obj1 } )
// { ["x", "y", "z"] , a: 1, b: 2}
配列はプロパティ名が配列(インデックス)番号になる
なお、配列を値にもつ変数を、スプレッド構文で展開してオブジェクトの中に入れた場合は、配列の各値のプロパティ名は配列(インデックス)番号になります。
obj1 = { a:1, b:2 }
arr = ["x", "y", "z"]
console.log( { ...arr, ...obj1 } )
// {0: 'x', 1: 'y', 2: 'z', a: 1, b: 2}
配列を展開した要素を後ろ側に追加しても、処理結果は同じになります。
obj1 = { a:1, b:2 }
arr = ["x", "y", "z"]
console.log( { ...obj1, ...arr } )
// {0: 'x', 1: 'y', 2: 'z', a: 1, b: 2}
オブジェクトとオブジェクトの間に新しい要素を追加する場合
obj1 = { a:1, b:2 }
obj2 = { c:3, d:4 }
str = "aaa"
console.log( { ...obj1, str, ...obj2 } )
//{a: 1, b: 2, str: 'aaa', c: 3, d: 4}
改行して記述する
配列と同じく、オブジェクトの場合も改行して記述していることが頻繁にあります。
obj1 = { a:1, b:2 }
obj2 = { c:3, d:4 }
str = "aaa"
newObj = {
...obj1,
str,
obj2,
num: 123
}
console.log( newObj )
// {a: 1, b: 2, str: 'aaa', obj2:{ c:3, d:4 }, num: 123}
↑↓ 同じ
obj1 = { a:1, b:2 }
obj2 = { c:3, d:4 }
str = "aaa"
newObj = { ...obj1, str, obj2, num: 123 }
console.log( newObj )
// {a: 1, b: 2, str: 'aaa', obj2:{ c:3, d:4 }, num: 123}
スプレッド構文とsliceを使って、指定した要素だけを削除する方法
「スプレッド構文」と「sliceメソッド」を組み合わせると、指定した要素だけを非破壊で削除することができます。
以下の「n」の部分に削除したい配列の番号を入れます。
処理後の変数 = [
...元の変数.slice(0, n),
...元の変数.slice(n)
]
実例
例えば、arrs=["000", 111, 222, 333, 444]
がある場合に、配列番号2の「222」のみを非破壊で削除したい場合は以下のようにします。
arrs=["000", 111, 222, 333, 444]
brr = [
...arrs.slice(0, 2),
...arrs.slice(3)
]
//処理結果
// ['000', 111, 333, 444]
指定した「222」が削除されていることがわかります。
なお、元の変数「arrs」はそのままです。
arrs
//中身
["000", 111, 222, 333, 444]
sliceメソッドとは何か?
sliceメソッドは配列に対して実行するもので、配列の指定した要素だけを抜き出すことができます。
第1引数で開始番号、第2引数で終了番号を指定することで、指定したところ要素だけを抜き出した配列を新たに作ります。
arr.slice(開始番号,終了番号)
sliceは非破壊的なため、元の配列はそのまま残っています。
sliceメソッドの実例|開始番号と終了番号を指定した場合
例えば以下のように[“aaa”,”bbb”,”ccc”,”ddd”,”eee”]が入った変数「arrs」があるとします。
ここから、0番目から、1番目の要素までを抜き出したい場合は、sliceの第一引数で「0」を、第二引数で「2」を指定します。
(※終了番号で指定した一つ前までの要素を抜き出す)
arrs=["aaa","bbb","ccc","ddd","eee"]
arrs.slice(0, 2)
//処理結果
//["aaa", "bbb"]
非破壊処理となるため、元の変数「arrs」を参照すると、データは元のままです。
arrs
//出力
//["aaa","bbb","ccc","ddd","eee"]
終了番号を指定しない場合
第2引数で終了番号を記載しない場合は、指定した番号以降の要素を全て抜き出します。
arrs=["aaa","bbb","ccc","ddd","eee"]
arrs.slice(2)
//処理結果
//["ccc", "ddd", "eee"]
スプレッド構文とsliceをつなげた処理
「…arrs.slice(0, 2)」のようにスプレッド構文とsliceをつなげた処理を上手く使うと、指定した要素を
では、sliceメソッドを実行した後の配列に対してスプレッド構文を実行します。
arrs=["000", 111, 222, 333, 444]
bar = [ ...arrs.slice(0, 2) ]
//処理結果
//['000', 111]
(ただし、1つの配列に対してsliceを実行するだけであればスプレッド構文は不要です)
スプレッド構文とsliceをつなげる処理は、指定した要素を非破壊で削除する場合に威力を発揮します。
考え方としてはsliceメソッドを使って、以下の2つの配列を作成します。
この配列をそれぞれスプレッド構文で展開して、[ ]で囲めば、指定した要素を削除した配列ができあがります。
削除する配列を変数で指定する方法
以下のように配列の中で削除したい要素を変数として渡すこともできます。
//削除する配列番号(nには数値が入る)
rmIndex = n
bar = [
...arr.slice(0,rmIndex),
...arr.slice(rmIndex+1)
]
実例
arrs = [ "aaa", "bbb", "ccc", "ddd", "eee" ]
//削除する配列番号
rmIndex = 2
//sliceを使った削除
arrs = [
...arrs.slice(0,rmIndex),
...arrs.slice(rmIndex+1)
]
console.log(arrs)
//出力
//["aaa", "bbb", "ddd", "eee"]
変数rmIndexで指定した番号、arrs[2]の要素を削除します。
削除する番号を動的に指定すれば、より柔軟に使うことができます。
指定した要素を追加する方法
sliceメソッドとスプレッド構文を使うと、非破壊で配列の中に指定した要素を追加することができます。
//元の配列
a = [1,2,3,4]
//要素を追加する配列番号と要素
addId = 2
addItem = "aaa"
//指定した場所に挿入
a =[
...a.slice(0, addId),
...[addItem],
...a.slice(addId)
]
console.log(a)
//出力
[1, 2, "aaa", 3, 4]
ポイント
sliceするときに、slice(0, n)とすると、n番目より前の要素を抜き出します。slice(n)とすると、n番目以降の要素を抜き出します。
a = [1,2,3,4]
a.slice(0, 2)
//処理結果
//[1, 2]
a = [1,2,3,4]
a.slice(2)
//処理結果
//[3, 4]
この2つをスプレッド構文で展開して、その間に追加したい要素を挟めば、指定した場所に要素を追加することができます。
a = [1,2,3,4]
b = "aaa"
c = [
...a.slice(0, 2),
b,
...a.slice(2)
]
//処理結果
//[1, 2, 'aaa', 3, 4]
指定した配列の要素を追加する方法
配列の指定した場所に、別の配列の要素を追加することもできます。
a = [1,2,3,4]
b = [ "aaa", "bbb", "ccc"]
c = [
...a.slice(0, 2),
...b,
...a.slice(2)
]
//処理結果
//[1, 2, 'aaa', 'bbb', 'ccc', 3, 4]