LoginSignup
2
1

More than 3 years have passed since last update.

...スプレッド構文の解説と使用する時の注意点

Last updated at Posted at 2021-04-09

はじめに

以前、reduxを導入した際にスプレッド構文を使用しましたが、なんとなくで理解していたばっかりに、全然なんて事ない所で詰まってデバッグしまくったので、スプレッド構文についてと使用する時の注意点を解説していきます。

解説しないこと

  • JavaScriptオブジェクトに置ける参照について
  • シャローコピーとディープコピー
  • 反復可能オブジェクト

シャローコピーとディープコピーについてはこちら

スプレッド構文とは

スプレッド構文とは、ES2015から使用できるようになった「...」を3つ並べると要素を展開できる機能のことで、配列の[ ]やオブジェクトの{ }を外すことができ、マージ変更として使う事もでき、展開した要素を再度変数に入れる事で複製を作る事ができる。

ちなみオブジェクトリテラルにはES2018から使用できるようになった。

MDNでは...

スプレッド構文 (...) を使うと、配列式や文字列などの反復可能オブジェクトを、0 個以上の引数 (関数呼び出しの場合) や要素 (配列リテラルの場合) を期待された場所で展開したり、オブジェクト式を、0 個以上のキーと値のペア (オブジェクトリテラルの場合) を期待された場所で展開したりすることができます。

書いてある通りですね...

反復可能オブジェクトとは、すごく簡単言うとmap()などの繰り返し関数の使用対象となるような要素のことです。

MDN:「反復可能オブジェクト

新しく追加されたオブジェクトリテラルは少し注意が必要で、オブジェクトリテラル自体は反復可能ではない為、keyとvalue別々で展開することはできません

オブジェクトリテラルは、単純な使用なら問題なく想定した通りに動きますが、ネストがあるオブジェクトに対しては参照して挙動が想定通りとはいきません。

実際のコードと結果を見た方が理解しやすいと思うので、まずは基本的な使い方から見ていきましょう。

基本的な使い方

  • 展開
  • マージ
  • 複製

上記でもお伝えしましたが、オブジェクトリテラルは、基本的な扱いとして気をつけたい点が2点あります。

1.オブジェクト自体は、反復可能オブジェクトではないのでそのまま展開することはできない。そのまま展開したいのであれば{ }で包んであげる必要がある。
2.オブジェクトをマージする時、対象となるオブジェクトに、同名のキーがあれば後に記述した方を選択する。

展開

spread.js
// 配列 
const array1 = ['a','b','c']
console.log(...array1) // "a" "b" "c"

// オブジェクト
const obj1 = {a: 1, b:2}
/* オブジェクトリテラルを展開する時は必ず{ }で囲む */
console.log({...obj1}) //Object { a: 1, b: 2 }
console.log(...obj1) // Error: Found non-callable @@iterator

/* オブジェクト自体は反復可能ではない為展開することはできない*/
const array = [...obj1];
console.log(array)
// excepect: ["a", 1, "b", "2"]
// result: TypeError: obj is not iterable

//文字列
const str = Hello World
console.log(...str); //  "H" "e" "l" "l" "o" " " "W" "o" "r" "l" "d"

const strings = [...str] //配列に展開
console.log(strings) // Array ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d"]

const strings = {...str} //オブジェクトに展開
console.log(strings) // Object { 0: "H", 1: "e", 2: "l", 3: "l", 4: "o", 5: " ", 6: "W", 7: "o", 8: "r", 9: "l", 10: "d" }

マージ

spread.js
// 配列
const array1 = ['a', 'b', 'c']
const array2 = ['d', 'e', 'f']
const array3 = [...array1, ...array2]
console.log(array2) // Array ["a", "b", "c", "d", "e", "f"]

const array2 = [...array1, 1, 2, 3]
console.log(...array3) // "a" "b" "c" "d" "e" "f" 1 2 3

//オブジェクト
const obj1 = { a: 1, b: 2 }
const obj2 = { c: 3, d: 4 }
const obj3 = { ...obj1, ...obj2 } //Object { a: 1, b: 2 }, { b: 3, c: 4 }
/* オブジェクトはキーが同名ならマージされる,かつ後の値が適用される */
console.log(obj3) //Object { a: 1, b: 3, c: 4 }

const obj4 = { ...obj3, d: 5, z: 9 } 
console.log(obj4) //  Object { a: 1, b: 2, c: 3, d: 5, z: 9 }

複製

spread.js
//配列

/*通常代入*/
const array1 = ['a','b','c']
const array2 = array1
console.log(array1 === array2) // true
/*スプレッド構文*/
const array1 = ['a','b','c']
const array2 = [...array1]
console.log(array1 === array2) // false

//オブジェクト

/*通常代入*/
const obj1 = {a: 1, b:2}
const obj2 = obj1
console.log(obj1 === obj2) // true
/*スプレッド構文*/
const obj1 = {a: 1, b:2}
const obj2 = {...obj1}
console.log(obj1 === obj2) // false

*値渡しのような結果だが値渡しではない

引数

spread.js
/* 引数が配列*/
const array = [1,2]

function arrayFunc(x,y){
 console.log(x)
 console.log(y)
}
arrayFunc(...array)
//  1
//  2

/* 引数がオブジェクト*/
const obj1 = {a: 1, b: 2}
const obj2 = {c: 1, d: 2}

function objFunc(obj){
 console.log(obj)
}

objFunc( {...obj1} ) // Object { a: 1, b: 2 }
objFunc( {...obj1, ...obj2} ) // Object { a: 1, b: 2, c: 1, d: 2 }
objFunc( {...obj1, obj2} ) // Object { a: 1, b: 2, obj2: { c: 1, d: 2 } }

/* new演算子にも使用できる */
class Person {
  constructor(name, age, countory){
    this.name = name;
    this.age = age;
    this.countory = countory;
  }
}

const personInfo = ['太郎', 23, 'Japan']
const person = new Person(...personInfo)

console.log(person.name) // "太郎"
console.log(person.age) // 23
console.log(person.countory) // "Japan"

残余引数は似ていますが、少し違うので注意

spread.js
// これは残余引数です
/**
 * スプレッド構文は要素を展開しますが、残余引数は集約します。
 * 受け取った引数は配列として受け取ります。
*/
function numbers(x, y, ...nums){
  console.log(x)
  console.log(y)
  nums.forEach(item => {
    console.log(item)
  })
}

numbers(1,2,3,4,5)
// 1
// 2
// 3
// 4
// 5

注意するべき点

JavaScriptは、変数に代入する方法が3つあり、単純な代入、シャローコピーとして代入、ディープコピーとして代入です。

注意してほしいのは、スプレッド構文を使用して代入するとシャローコピーになる点です。

これ知らなかったら結構どハマりポイントなんでここだけでも覚えてて損はないです!

シャローコピーは、コピー元とそのコピーは別のオブジェクトのように扱われるが、オブジェクトがネストしている部分だけ元のオブジェクトを参照するなんとも厄介な複製仕様です。

sharrow.js

//ネストのあるオブジェクト
const obj1 = { 
 b: { 
  c: 1, 
  d: 2,
 },
  e: 3
}

const obj2 = {...obj1}

/* 別のオブジェクトのように扱われているが、ネストしている箇所だけコピー元を参照する */
console.log(obj1 === obj2) // false
console.log(obj1.b === obj2.b) // true...???

まとめ

今回は、スプレッド構文の解説のためスプレッド構文を代入した時の挙動のみになりましたが、シャローコピーとディープコピーについては別記事で解説していきたいと思います。

各対象の挙動と注意点さえわかっていればハマることなく直感的に使える便利な機能だと思うので是非使って色々試して見てください。

以上、スプレッド構文についての解説と注意点でした!

最後まで読んで頂きありがとうございました。

参考サイト

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1