JavaScriptの全貌を知るには、人生は短すぎます。大まかに把握したと思っていても、少し時間が経つと、またわけのわからない構文が現れます。
特に最近増えてきた記法として、代入の左辺に、括弧が使われているというものがあります。今回は、これの正体を探ってみましょう。
括弧のついた代入たち
JavaScriptにおいて、代入とは一般に以下のようなものを指します:
const myNumber = 100;
しかし、モダンなライブラリを使用していると、以下のようなサンプルコードを見かけることがあります:
const [job] = client.exec();
また、Node.jsにおいて、以下のような構文を見かけることも増えてきました:
const {SampleClass} = require('sample-lib');
どちらも少し不思議です。左辺の変数名が括弧([]または{})で括られています。サンプルコードには、これらの意味の説明はついていません。なんだかよくわからないまま使わされている、というのが実情です。
普通の代入とどう違うのでしょう?いったい何が起こっているのでしょうか。
分割代入(Destructuring Assignment)
実は、JavaScriptには分割代入(Destructuring Assignment)という機能があります。
簡単な例から行きましょう。ひとつの配列があるとして、その要素を個別の変数に代入していきたいと考えます。以下のようなコードになります:
const array = [1, 2, 3];
const e0 = array[0];
const e1 = array[1];
const e2 = array[2];
実に単純明快なコードです。配列を用意して、その配列の要素を個別の変数に代入しているだけです。
分割代入を使うと、これを簡単に書けるようになります。以下が例です:
const array = [1, 2, 3];
// e0にarray[0]、e1にarray[1]、e2にarray[2]を代入!
const [e0, e1, e2] = array;
分割代入では、左辺の変数名を括弧でくくります。そうすると右辺の配列の対応したインデックスの値が、変数に自動的に代入されます。
上の例で言うと、左辺の0番目の変数e0には、右辺のarrayの0番目の値が入ります。1番目の変数e1には、右辺のarrayの1番目の値が入ります。3番目も同様です。
分割代入では、全ての値を受け取るだけでなく、右辺の不要な部分を読み捨てることもできます。
const array = [1, 2, 3];
// array[0]は読み捨て、array[1]はe1に代入、array[2]は読み捨て
const [,e1] = array;
読み捨てる部分が途中にある場合は、カンマだけを書きます。これで左辺には対応する変数がなくなるので、値は代入されません。上の例で言えば、[,e1]となっているので、array[0]はどこにも代入されませんが、array[1]はe1に代入されます。
読み捨てる部分が後ろにある場合は、左辺の数自体を減らします。上の例ではそもそも左辺が2要素しかなく、右辺の3要素とかみ合っていません。なので3番目のarray[2]は読み捨てられます。
「残りその他」とざっくり代入することもできます。「残りその他」を取得するには、左辺にピリオドを3つ並べて「…変数名」とします。
const [,e1,, ...rest] = [0, 1, 2, 3, 4, 5];
console.log(e1); // 1
console.log(rest); // [3, 4, 5]
この例では、配列の0番目と2番目を読み捨てて、2番目をe1、3番目以降を変数restに代入しています。
このように、分割代入を用いることで、配列の値を、自由かつ柔軟に左辺に代入することができます。
これで冒頭の例で出した、以下のコードの意味が理解できます:
const [job] = client.exec();
このコードにおいて、client.exec()は何かしらの配列を返すのでしょう。その配列の0番目だけを変数jobとして取得したい、ということだと推察できます。右辺を評価後の値に置き換えてみると、例えば以下のようになるでしょうか:
const [job] = [jobInfo, execParams, apiResponse];
execParamsとapiResponseを読み捨てて、jobInfoだけを変数jobとして取得したい、というコードなのでしょう。
もちろん以下のように書いても構いません:
const execResultArray = client.exec();
const job = execResultArray[0];
ただ、分割代入を用いたほうが、綺麗に書けるでしょう。
オブジェクトの分割代入
分割代入は配列だけの機能ではありません。オブジェクトに対しても使えます。オブジェクトに対して使用する場合は、左辺の括弧を{}にします。また、変数名は右辺のオブジェクトのプロパティ名と合わせます。
const obj = {a: 10, b: 20, c: 30};
const {a, c} = obj;
console.log(a); // 10
console.log(c); // 30
これで変数aにobj.aの値が、変数cにobj.cの値が読み込まれます。配列と違い、順番は関係ありません。ただし、変数名はオブジェクトのプロパティ名と合わせる必要があります。
しかし左辺には別の名前をつけたい場合もあります。その場合は左辺の変数名にコロンをつけます。
const obj = {a: 10, b: 20, c: 30};
const {a: val1, c: val2} = obj;
console.log(val1); // 10
console.log(val2); // 30
これで変数val1にobj.aが、変数val2にobj.cが入ります。
以上のことから、冒頭で触れた以下のコードの謎も解けます:
const {SampleClass} = require('sample-lib');
これはrequire(‘sample-lib’)で読み込んだオブジェクトの中の、SampleClassを、変数SampleClassに代入しているだけです。つまり、以下の処理と同等です:
const SampleClass = require('sample-lib').SampleClass;
この記法は、読み込む値が複数になった時に特に強力です。
const {SampleClass, NiceClass, GreatClass} = require('sample-lib');
このコードは以下とほぼ同等です:
const sampleLib = require('sample-lib');
const SampleClass = sampleLib.SampleClass;
const NiceClass = sampleLib.NiceClass;
const GreatClass = sampleLib.GreatClass;
分割代入を使用すると、複雑な代入を簡潔なコードで表現することができます。
より詳しい情報
より詳しい情報については、MDNの分割代入のページをご覧ください。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment