Subterranean Flower

JavaScriptの代入において時々現れる、括弧で囲まれた変数名は何なのか

Author
古都こと
ふよんとえんよぷよぐやま!!!

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