memolu

いろいろメモってます

ES6 - Destructuringを解説してみる

本連載について

本連載では少しづつES6について記載していこうと思います。 第六回はDestructuringを学びます。 発表されてから久しいですが、まだ習得されていない方は今からでも勉強してみてはいかがでしょうか。

Destructuring

Destructuringは「分割代入(Destructuring assignment)」と呼ばれ、配列やオブジェクトに格納されている値を一度に個別の変数に代入できる機能です。
主にコードの見通しの向上を期待されている機能ですが、使い過ぎると逆に見通しが悪くなる側面があり、使い所が重要になっています。またこれだけを使うのではなく、他の機能と組み合わせてシナジーとして利用することも多くなっています。

ブラウザサポート

ブラウザサポートについては以下をご確認ください。

http://kangax.github.io/compat-table/es6/

Destructuringの使い方

さっそく使い方を説明します。次のコードをご覧ください。

let obj = {
    foo: 1,
    bar: 2
};
let { foo, bar } = obj;
console.log(foo, bar); // 1 2

objオブジェクトにfoobarというプロパティを追加し、値にはそれぞれ1,2のように割り振りました。
let {foo bar} = objが今回解説させていただくDestructuringの部分になります。consoleの出力結果を見ると、obj内にある同じ名前のプロパティが代入されていることがわかります。
Destructuringは{ foo, bar }のようにすることで、オブジェクトにある同名プロパティを代入することができる機能です。オブジェクトの場合、同じプロパティ名で判断するため順番を逆にしても変化はありません。

// プロパティ名を見て判断するため順番が変わっても問題ない
let obj = {
    foo: 1,
    bar: 2
};
let { bar, foo } = obj;
console.log(foo, bar); // 1 2

配列の場合

配列の場合も見てみましょう。

let numbers = [0, 1, 2, 3];
let [one, two] = numbers;
console.log(one); // 0
console.log(two); // 1

オブジェクトでは{}だった部分が[]のような配列リテラルに変わっています。これは面倒に見えますが、代入する対象がどの型なのかを判別できる利点になっています。 また、配列の場合はプロパティ名のようなものがないため、順番に左右される点に注意してください。

配列の残り全てを取り出す

...変数名のように記述することで、残りの配列全てとして代入させることが可能です。

let numbers = [0, 1, 2, 3];
let [one, two, ...rest] = numbers;
console.log(one); // 0
console.log(two); // 1
console.log(rest); // [2, 3]

配列から目的の値だけを取り出す

配列の途中にある値だけを取り出す場合、,で空欄を生成すれば抽出することが可能です。

let numbers = [0, 1, 2, 3];
let [, , , four] = numbers;
console.log(four); // 3

文字列の場合

文字列の場合は一文字づつを変数に代入します。文字列の場合も、[]で配列リテラルを使用します。

let str = 'hello! ymmt!';
let [one, two, ...rest] = str;
console.log(one); // h
console.log(two); // e
console.log(rest); // ["l", "l", "o", "!", " ", "y", "m", "m", "t", "!"]

オブジェクトの場合

上述しましたが、オブジェクト型の場合は[]ではなく{}のオブジェクトリテラルを使用します。

let obj = {
    foo: 1,
    bar: 2
};
let { foo, bar } = obj;
console.log(foo, bar); // 1 2

任意の変数名に代入する

上のコードではプロパティ名と変数名が同一になっていますが、変数名は好きな名前のものに代入することも可能です。プロパティ名:変数名のように記載します。

let obj = {
    foo: 1,
    bar: 2
};
let { foo: first, bar: second } = obj;
console.log(first, second); // 1 2

初期値の設定

ここまでは全てオブジェクトや配列の中に値がある場合のみでしたが、値がない場合もパターンとして存在します。
配列とオブジェクトで記載方法が少しだけ変わりますのでそれぞれご紹介します。 一点だけ注意が必要で、初期値の適用ができるのは、代入元の値がundefinedだった場合のみです。nullなどは値があるものとしてしまうため、初期値は上書きされてしまいます。

配列の初期値

配列は変数名=値とすることで初期値を設定することが可能です。

let arr = [];
let [one = 10, two] = arr;
console.log(one); // 10
console.log(two); // undefined

オブジェクトの初期値

オブジェクト型も配列の場合とほぼ一緒です。プロパティ名=値と記載すれば動作します。当然、任意の変数名に代入することも可能です。

let obj = {
    bar: 10
};
let {foo: first = 1, bar: second} = obj;
console.log(first); // 1
console.log(second); // 10

上のコードでは、objにはfooという値はありません。しかし初期値1を入力しているため、undefinedになりません。さらに、代入先を変数firstに変更しています。

具体的な使い方

いくつか例を出して説明して参りましたが、これだけですと実践で使用するポイントが不鮮明なため、ここではより実践向きな内容をご紹介いたします。

変数をスワップする

分割代入を使用して、複数の変数の値を入れ替えることができます。

var a = 1;
var b = 2;

var [a, b] = [b, a];

console.log(a); // 2
console.log(b); // 1

これを分割代入なしで実装しようとすると、変数がもう一つ、いわゆるテンポラリ変数が必要になってしまいます。今回の分割代入を使用するほうが記述量が減るメリットがあります。

正規表現との併用

正規表現exec()メソッドを使用した結果は配列で返ってきます。これを分割代入で受け取ることにより、各パラメータに振り分けることが可能です。

const url = 'http://memolu.hatenablog.com/entry/2016/07/10/142128';
const parseURL = /^(\w+)\:\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)\/(.*)$/.exec(url);
console.log(parseURL); // ["http://memolu.hatenablog.com/entry/2016/07/10/142128", "http", "memolu.hatenablog.com", "entry", "2016", "07", "10", "142128", index: 0, input: "http://memolu.hatenablog.com/entry/2016/07/10/142128"]

const [, protocol, host, ...rest] = parseURL;

console.log(protocol); // http
console.log(host); // memolu.hatenablog.com
console.log(rest); //["entry", "2016", "07", "10", "142128"]

ループ処理の併用

配列をループ処理するforEach関数でも分割代入を使用することができます。
次のコードでは、変数humansに配列を代入しています。配列にはそれぞれオブジェクトリテラルを使用してnameとageというプロパティがそれぞれ設定されています。

const humans = [
    {
        name: 'ymmt',
        age: 99
    },
    {
        name: 'yt',
        age: 10
    }
];

humans.forEach( ({name, age}) => {
    console.log(name, age);
    // ymmt 99 
    // yt 10
});

forEachのコールバック関数にはArrow functionを使用し、その引数に分割代入を使用しています。({name,age})の部分にはループ毎のnameとageプロパティが代入され、コールバック関数内で処理が行われています。
分割代入を使用しない場合、ES5でコードを記載すると以下のようになります。

// ES6を使用しない場合
var humans = [{
    name: 'ymmt',
    age: 99
}, {
    name: 'yt',
    age: 10
}];

humans.forEach(function(human) {
    console.log(human.name, human.age);
});

コードが簡単なため、そこまで差異がありませんがこのように少し冗長的になってしまいますね。 引数に渡っている数値の数などがこちらですとわからないため、分割代入を行ったほうが引数がいくつ渡っているのか、その引数の内容はどんなものかが明確になっています。

まとめ

このように分割代入機能を使用するとコードの見通しの向上などが期待できます。
適材適所として使用することで、非常に効果が高くなるため、ぜひ取り入れていただきたい機能にまとまっています。babelも非常に信頼性の高い機能としてまとまっていますので、プロジェクトに活用するのは問題ない時期に突入していると筆者は考えます。