JavaScript 分割代入とスプレッド構文
近年のJavaScript開発において非常に使用頻度が高い記法である
分割代入、そしてスプレッド構文についてもなるべく深く掘り下げて、自分自身に向けて解説してみます
今回も基本的な部分の備忘録として残しておきます
分割代入
JavaScriptでオブジェクトや配列を扱う時に非常によく使われる記法である分割代入の解説です
最近のReact開発やWordPressのブロック開発などでは当たり前に使用されていますが、読み解く場合はなかなか難しいことも、、
まずは基本的なおさらいです
分割代入とは配列やオブジェクトの要素を直接変数として取り出して使用する記法です
ということでパターン別に例を上げてみます
配列の分割代入
配列の分割代入では、右オペランドの配列の要素が左オペランドの変数(変数A、変数B)にそれぞれ代入されていきます。左辺の変数は普通に変数宣言した場合と同様に使用することが可能です
let [変数A, 変数B, 変数C = 初期値] = 配列;
let arry = [1, 2, 3]; // 変数x, y, zに配列の要素を代入する let [x, y, z] = arry; console.log(x, y, z); // > 1 2 3 // 不要な要素は空白としておくことでスキップできる let [x1, , z1] = arry; // 2つ目の要素を空白にする console.log(x1, z1); // > 1 3 // 宣言済みの変数に対しても代入できる let x2; [x2] = arry; console.log(x2); // > 1 // デフォルト値を設定できる let [, , , a3 = 4] = arry; console.log(a3); // > 4 // スプレッド演算子(後述)と合わせて使うこともできる let [x4, ...rest] = arry; console.log(x4, rest); // > 1[2, 3]
オブジェクトの分割代入
配列の分割代入では配列の要素の順番通りに変数に対して代入されていきますが、オブジェクトの分割代入ではプロパティ名と変数名を一致させる必要があります
let {プロパティ名A, プロパティ名B = 初期値} = オブジェクト;
変数名を変更したい場合はプロパティ名に続けてコロン、変数名とします{ プロパティ名: 新たな変数名 }
ネストされたオブジェクトを取得したい場合は、オブジェクト名に続けてコロン波括弧プロパティ名とします{ 子オブジェクト名: {孫プロパティA, 孫プロパティB} }
// オブジェクトの分割代入はプロパティ名の変数を宣言する。プロパティの順番は関係ない let { banana, orange, apple } = { apple: "リンゴ", banana: "バナナ", orange: "オレンジ", }; console.log(banana); // > バナナ // プロパティと変数名を変えたい場合 let { banana: b, apple: a } = { apple: "リンゴ", banana: "バナナ" }; console.log(b, a); // > バナナ リンゴ // デフォルト値を代入する場合 let { banana: b2 = "おいしいバナナ" } = { apple: "リンゴ" }; console.log(b2); // > おいしいバナナ // スプレッド演算子と合わせて使うこともできる let { banana: b3, ...fruits } = { apple: "リンゴ", banana: "バナナ", orange: "オレンジ", }; console.log(fruits); // > {apple: "リンゴ", orange: "オレンジ"} バナナ以外が格納されたオブジェクトが生成 // ネストしたオブジェクトも展開できる let { fruits: { apple: a3 }, } = { fruits: { apple: "リンゴ", banana: "バナナ" } }; console.log(a3); // > リンゴ // 動的にプロパティ名を指定できる let prop = "apple"; let { [prop]: a4 } = { apple: "リンゴ", banana: "バナナ", orange: "オレンジ" }; console.log(a4); // > リンゴ
関数の引数を分割代入で受け取る
分割代入は、関数の引数を受け取るときにも使用可能で、オブジェクトや配列を直接渡し、特定の要素だけを抽出して使用可能です
下記は関数に配列の分割代入を行う場合です
function 関数名( [1つ目の要素, ,3つ目の要素] ) {};
オブジェクトの場合はプロパティ名を指定し、その値を直接使用することが出来ます
function 関数名( {プロパティ名} ) {};
const fruitsArry = ["banana", "orange", "grape"]; const fruitsObj = { banana: "バナナ", orange: "オレンジ" }; function bunkatuArry([, , fruit3]) { // 1つ目、2つ目の要素を空白にする console.log(fruit3); } bunkatuArry(fruitsArry); // 配列を実引数として渡す // > grape function bunkatuObj({ orange }) { // orangeのみ引数として抽出 console.log(orange); } bunkatuObj(fruitsObj); // オブジェクトを実引数として渡す // > オレンジ
引数で分割代入を使用する場合、例えば配列を含むオブジェクトの場合でもカッコを使い分けて、代入可能です
const taro = { name: { first: "丸", last: "おジャコ" }, age: 18, hobbies: ["野球", "サッカー"], }; // 分割代入でオブジェクトから変数に値を抽出 function greeting({ name: { first, last }, age, hobbies: [hobby1, hobby2] }) { console.log(`名前は${last + first}です。${age}歳です。`); console.log(`趣味は${hobby1}と${hobby2}です。`); } greeting(taro); // オブジェクト(taro)を渡す // > 名前はおジャコ丸です。18歳です。 // > 趣味は野球とサッカーです。
スプレッド演算子
スプレット演算子はオブジェクトや配列の要素を展開した理、まとめたりするための演算子です。
スプレッド演算子はオペランドの前にドットを3つ続ける形式(…オペランド)で記述します
以下のようなことが簡単に書けます
- 関数実行時に、配列の要素の値を複数の引数に展開して設定する
- 関数実行時に、複数の引数をオペランドや配列の要素としてまとめる
- 配列やオブジェクトの複製(コピー)や結合を行う
※スプレッド演算子を反復可能オブジェクトに適用すると、配列の複製などの挙動がイテレータの実装に従うようになります。
関数宣言時の使用
関数宣言時の引数に使われるスプレット演算子は引数をまとめて一つの配列として保持する役割があります
//渡された引数を配列にまとめる
function 関数(...arg) {};
いわゆる可変長引数で引数の数が決まっていない場合など、まとめて引数をを大量に受け取って内部で配列をループして処理したりするなど重宝します
関数実行時の使用
関数を実行する際に引数にとして渡す配列にスプレッド演算子を使用すると、配列の中身が展開されて引数として関数に渡されます
const params = [arg1, arg2];
function 関数(引数1, 引数2) {};
関数(...params); //配列を展開してパラメータとして渡す
配列の複製に使用
const 結合された配列 = [...もとの配列];
配列の複製にもスプレット演算子を使用することが出来ます。この場合は配列の一階層目の値が新しい配列の要素として複製(コピー)されることになります
そのため、この複製方法はディープコピー、、もとの配列に影響を与えない方法です。
const original = ["元の値"]; const duplicated = [...original]; // 元の配列の要素を持つ新しい配列を作成 duplicated[0] = "変更後の値"; // 複製後の値を変更 console.log(`original[ ${original[0]} ] duplicated[ ${duplicated[0]} ]`); // > original[ 元の値 ] duplicated[ 変更後の値 ]
配列の作成
スプレッド演算子を使うと、配列をマージ(結合)したり、任意の①に要素を追加した配列を簡単に作成できます
const 結合された配列 = [...もとの配列]; //配列の結合
const 追加された配列 = [要素1, ...配列展開, 要素2];
const arry1 = [10, 20, 30]; const arry2 = [40, 50, 60]; console.log([...arry1, ...arry2]); // 配列のマージ // > [10, 20, 30, 40, 50, 60] console.log([...arry2, ...arry1]); // 配列のマージ(arry2から要素を設定) // > [40, 50, 60, 10, 20, 30] console.log([0, ...arry2, 70, ...arry1]); // 任意の場所に要素を追加 // > [0, 40, 50, 60, 70, 10, 20, 30]
オブジェクトでの使用
スプレッド演算子はES2018以降ではオブジェクトに対しても使用できるようになったようなので見てみます
const 複製されたオブジェクト = {...元のオブジェクト}; //複製
const 結合されたオブジェクト = {...オブジェクト1, ...オブジェクト2}; //結合
const オブジェクト = {"プロパティ1": "値1", ...元のオブジェクト, "プロパティ2": "値2"}; //任意の位置に挿入
ちなみにオブジェクトのマージ(結合)する場合、元のオブジェクトに同名プロパティがある場合は置き換わるようです
const obj1 = { prop1: 10, prop2: 20 }; const obj2 = { prop3: 30, prop4: 40 }; console.log({ ...obj1, ...obj2 }); // オブジェクトのマージ // > {prop1: 10, prop2: 20, prop3: 30, prop4: 40} console.log({ prop0: 0, ...obj2, prop5: 50 }); // 任意のプロパティの追加 // > {prop0: 0, prop3: 30, prop4: 40, prop5: 50} // プロパティ重複時は、あとから定義したほうで上書き console.log({ prop1: 0, ...obj1 }); // > {prop1: 10, prop2: 20}
なお関数実行時の引数をオブジェクトとスプレッド演算子を使って記述することは出来ないため注意が必要です
Object.assignでまとめる
オブジェクトの結合は何もスプレッド演算子だけではなくObject.assign
というメソッドにも触れておきます
Object.assignは第一引数に指定したオブジェクトに第二引数以降の全てのオブジェクトのプロパティを第一引数に指定したオブジェクトにコピーしてくれる
const target = { a: 1, b: 2}
const source = { c: 3, d: 4}
const returnedTarget = Object.assign(target, source);
>{ a: 1, b: 2, c: 3, d: 4 }
Object.assignはオブジェクトのコピーにも頻繁に利用されます。第一引数に空のオブジェクトを指定することで全く新しいコピーオブジェクトを作成することができます。
また同名のプロパティを第一引数のtarget
が持っている場合、そのプロパティは上書きされるため、
値の更新にも使用できます
const form = { firstName: null, lastName: null, Email: null, zipCode: null, Address: null, Phone: null } const input = { firstName: 'John', lastName: 'Doe', Email: 'john@example.com' } Object.assign(form, input) //>{ firstName: 'John', lastName: 'Doe', Email: 'john@example.com', zipCode: null, Address: null, Phone: null }
補足
反復可能オブジェクトをスプレッド構文で複製した場合にはイテレータの挙動に従ってスプレッド演算子は動作します。そのためオブジェクトのSymbol.iteratorプロパティの処理を変更すると、その挙動が変化します
Array.prototype[Symbol.iterator] = function* () { yield "Hello"; yield "World"; }; const arry = [1, 2, 3]; const newArry = [...arry]; console.log(newArry); // > [ "Hello", "World" ]
コメントをお待ちしております