ECMAScript 2015 の分割代入は奥が深かった

  • 31
    いいね
  • 0
    コメント

はじめに

配列やオブジェクトの個々の値を各々の変数に代入する事ができる「分割代入」というのが登場しました。
結構便利そうではありますが、具体的にどのような事ができるのでしょう。

ECMAScript 2015 (ES2015 / ES6) にまだ慣れない人向けと、自分のためのメモとして置いておきます。

基本編

おそらく一番使うであろう書き方です。

配列

通常、配列から値を取り出すときはインデックス(添字)を指定してやらなければいけませんが、分割代入ではまとめて変数に代入できるので便利です。

従来の書き方例
/* 一個ずつ取り出す場合 */
var individuals = ['早坂美玲', '森久保乃々', '星輝子'];

var
  cute = individuals[0],
  cool = individuals[1],
  passion = individuals[2];

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

/* 一部だけ取り出す場合 */
var kbyd = ['輿水幸子', '姫川友紀', '小早川紗枝'];

var yakyu = kbyd[1];

console.log(yakyu);  // "姫川友紀"

/* 取得しようとした値が未定義だった場合に代わりの値を代入 */
var loveLaika = ['新田美波', 'アナスタシア'];

var
  minami = loveLaika[0],
  anastasia = loveLaika[1],
  ranko = loveLaika[2] || '神崎蘭子';

console.log(minami);     // "新田美波"
console.log(anastasia);  // "アナスタシア"
console.log(ranko);      // "神崎蘭子"

/* 配列から値を取り出して、それ以外の新しい配列を作る */
var engine = ['向井拓海', '藤本里奈', '松永涼', '大和亜季', '木村夏樹'];

var
  head = engine[0],
  member = engine.slice(1);

console.log(head);    // "向井拓海"
console.log(member);  // ["藤本里奈", "松永涼", "大和亜季", "木村夏樹"]
分割代入での書き方例
/* 一個ずつ取り出す場合 */
let individuals = ['早坂美玲', '森久保乃々', '星輝子'];

let [cute, cool, passion] = individuals;

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

/* 一部だけ取り出す場合 */
let kbyd = ['輿水幸子', '姫川友紀', '小早川紗枝'];

let [, yakyu] = kbyd;

console.log(yakyu);  // "姫川友紀"

/* 取得しようとした値が未定義だった場合に代わりの値を代入 */
let loveLaika = ['新田美波', 'アナスタシア'];

let [minami, anastasia, ranko = '神崎蘭子'] = loveLaika;

console.log(minami);     // "新田美波"
console.log(anastasia);  // "アナスタシア"
console.log(ranko);      // "神崎蘭子"

/* 配列から値を取り出して、それ以外の新しい配列を作る */
let engine = ['向井拓海', '藤本里奈', '松永涼', '大和亜季', '木村夏樹'];

let [head, ...member] = engine;  // スプレッド演算子を使う

console.log(head);    // "向井拓海"
console.log(member);  // ["藤本里奈", "松永涼", "大和亜季", "木村夏樹"]

こうして見ると、PHPの list($a, $b, $c) = ['a', 'b', 'c']; みたいな感じですね。
ECMAScript 5 までの頭でいると、配列リテラルが左側にある違和感はまだ拭えないですね……

オブジェクト

配列と同じようにオブジェクトも分割代入が可能です。

従来の書き方例
/* 一個ずつ取り出す場合 */
var individuals = {cute: '早坂美玲', cool: '森久保乃々', passion: '星輝子'};

var
  cute = individuals.cute,
  cool = individuals.cool,
  passion = individuals.passion;

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

/* 一部だけ取り出す場合 */
var kbyd = {kawaii: '輿水幸子', yakyu: '姫川友紀', dosue: '小早川紗枝'};

var yakyu = kbyd.yakyu;

console.log(yakyu);  // "姫川友紀"

/* 取得しようとした値が未定義だった場合に代わりの値を代入 */
var loveLaika = {minami: '新田美波', anastasia: 'アナスタシア'};

var
  minami = loveLaika.minami,
  anastasia = loveLaika.anastasia,
  ranko = loveLaika.ranko || '神崎蘭子';

console.log(minami);     // "新田美波"
console.log(anastasia);  // "アナスタシア"
console.log(ranko);      // "神崎蘭子"

/* プロパティ名とは別の名前の変数に代入 */
var newGenerations = {shimamu: '島村卯月', shiburin: '渋谷凛', mio: '本田未央'};

var
  uzuki = newGenerations.shimamu,
  rin = newGenerations.shiburin,
  mio = newGenerations.mio;

console.log(uzuki);  // "島村卯月"
console.log(rin);    // "渋谷凛"
console.log(mio);    // "本田未央"
分割代入での書き方例
/* 一個ずつ取り出す場合 */
let individuals = {cute: '早坂美玲', cool: '森久保乃々', passion: '星輝子'};

let {cute, cool, passion} = individuals;  // プロパティ名と同じにする必要がある

/*
  この記述は以下の省略型
  let {cute: cute, cool: cool, passion: passion} = individuals;

  ちなみに順番は関係ないのでこれでもいい
  let {passion, cute, cool} = individuals;
*/

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

/* 一部だけ取り出す場合 */
let kbyd = {kawaii: '輿水幸子', yakyu: '姫川友紀', dosue: '小早川紗枝'};

let {yakyu} = kbyd;

console.log(yakyu);  // "姫川友紀"

/* 取得しようとした値が未定義だった場合に代わりの値を代入 */
let loveLaika = {minami: '新田美波', anastasia: 'アナスタシア'};

let {minami, anastasia, ranko = '神崎蘭子'} = loveLaika;

console.log(minami);     // "新田美波"
console.log(anastasia);  // "アナスタシア"
console.log(ranko);      // "神崎蘭子"

/* プロパティ名とは別の名前の変数に代入 */
let newGenerations = {shimamu: '島村卯月', shiburin: '渋谷凛', mio: '本田未央'};

let {shimamu: uzuki, shiburin: rin, mio} = newGenerations;
// プロパティ名と変数名が同じならそのままでもいい

console.log(uzuki);  // "島村卯月"
console.log(rin);    // "渋谷凛"
console.log(mio);    // "本田未央"

オブジェクトに関しては、配列と違って順番という概念はないので(ABC順になることもある)プロパティ名で指定して代入する形です。
通常ではプロパティ名と変数名は同じになってしまいますが、変数名をプロパティ名と別にしたい場合は {プロパティ名: 変数名, ...} のように指定するだけです。

複数階層編

配列やオブジェクトは複数の階層になっている場合にも分割代入は役に立ちます。

配列

配列の中に配列がある場合
let kbyd = [
  ['輿水幸子', 'さっちー'],
  ['姫川友紀', 'ユッキー'],
  ['小早川紗枝', '紗枝はん'],
];

// 2つ目の配列の値を取りたい
let [, [name, nickname]] = kbyd;

console.log(name);      // "姫川友紀"
console.log(nickname);  // "ユッキー"
配列の中にオブジェクトがある場合
let kbyd = [
  {name: '輿水幸子', nickname: 'さっちー'},
  {name: '姫川友紀', nickname: 'ユッキー'},
  {name: '小早川紗枝', nickname: '紗枝はん'},
];

// 3つ目の値を取りたい
let [, , {name, nickname}] = kbyd;

console.log(name);      // "姫川友紀"
console.log(nickname);  // "ユッキー"

// 1つ目の値を取りたい
let [{name: kawaiiName, nickname: kawaiiNickname}] = kbyd;

console.log(kawaiiName);      // "輿水幸子"
console.log(kawaiiNickname);  // "さっちー"

オブジェクト

オブジェクトの中に配列がある場合
let idolUnit = {
  newWave: ['村松さくら', '大石泉', '土屋亜子'],
  underTheDesk: ['佐久間まゆ', '森久保乃々', '星輝子'],
  littleCherryBlossom: ['櫻井桃華', '岡崎泰葉', '龍崎薫'],
};

// 一部を取り出す
let {underTheDesk: [cute, cool, passion]} = idolUnit;

console.log(cute);     // "佐久間まゆ"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"
オブジェクトの中にオブジェクトがある場合
let idolUnit = {
  newWave: {cute: '村松さくら', cool: '大石泉', passion: '土屋亜子'},
  underTheDesk: {cute: '佐久間まゆ', cool: '森久保乃々', passion: '星輝子'},
  littleCherryBlossom: {cute: '櫻井桃華', cool: '岡崎泰葉', passion: '龍崎薫'},
};

// 一部を取り出す
let {newWave: {cute, cool, passion}} = idolUnit;

console.log(cute);     // "村松さくら"
console.log(cool);     // "大石泉"
console.log(passion);  // "土屋亜子"

// 一部を取り出す
let {
  littleCherryBlossom: {
    cute: cuteName,
    cool: coolName,
    passion: passionName,
  },
} = idolUnit;

console.log(cuteName);     // "櫻井桃華"
console.log(coolName);     // "岡崎泰葉"
console.log(passionName);  // "龍崎薫"

深い階層にある値を取る

AjaxなどでJSONを取得して処理することもあるかと思います。その場合って結構階層が深かったりします。
TwitterのAPIが返すJSONを例にしてみます。

このようなJSONがあるとします。

TwitterのJSON例
{
  "id": 1234567890,
  "name": "サーバル",
  "screen_name": "KemonoServal",
  "location": "さばんなちほー",
  "description": "サーバルキャットのサーバルだよ!",
  "url": "https://t.co/647RVtf37v",
  "entities": {
    "url": {
      "urls": [
        {
          "url": "https://t.co/647RVtf37v",
          "expanded_url": "http://kemono-friends.jp/",
          "display_url": "kemono-friends.jp"
        }
      ]
    }
  },
  "followers_count": 1000,
  "friends_count": 1000,
  "created_at": "Mon Mar 16 00:00:00 +0000 2015",
  "time_zone": "Japari Park",
  "statuses_count": 100,
  "lang": "ja",
  "status": {
    "created_at": "Wed Apr 05 15:00:00 +0000 2017",
    "id": 123456789123456789,
    "text": "@KemonoKaban がおー! たべちゃうぞー!",
    "entities": {
      "user_mentions": [
        {
          "screen_name": "KemonoKaban",
          "name": "かばん",
          "id": 1234567891,
          "id_str": "1234567891"
        }
      ]
    },
    "in_reply_to_status_id": 123456789012345678,
    "in_reply_to_user_id": 1234567891,
    "in_reply_to_screen_name": "KemonoKaban",
    "lang": "ja"
  }
}

このJSONからいろいろ値を取ってみます。

従来の書き方例
// 変数 json にさっきのJSON文字列が入っているとする
var jsonData = JSON.parse(json);

var
  name                = jsonData.name,
  description         = jsonData.description,
  region              = jsonData.location,
  website             = jsonData.entities.url.urls[0].expanded_url,
  tweet               = jsonData.status.text,
  inReplyToName       = jsonData.status.entities.user_mentions[0].name,
  inReplyToScreenName = jsonData.status.in_reply_to_screen_name;

console.log(name);                 // "サーバル"
console.log(description);          // "サーバルキャットのサーバルだよ!"
console.log(region);               // "さばんなちほー"
console.log(website);              // "http://kemono-friends.jp/"
console.log(tweet);                // "@KemonoKaban がおー! たべちゃうぞー!"
console.log(inReplyToName);        // "かばん"
console.log(inReplyToScreenName);  // "KemonoKaban"

オブジェクトや配列が混じっているとなんだかよくわからない感じですね。

分割代入を使った例1
// 変数 json にさっきのJSON文字列が入っているとする
let jsonData = JSON.parse(json);

let {
  name, description,
  location: region,
  entities: {
    url: {
      urls: [
        {expanded_url: website},
      ],
    },
  },
  status: {
    text: tweet,
    entities: {
      user_mentions: [
        {name: inReplyToName},
      ],
    },
    in_reply_to_screen_name: inReplyToScreenName,
  },
} = jsonData;

console.log(name);                 // "サーバル"
console.log(description);          // "サーバルキャットのサーバルだよ!"
console.log(region);               // "さばんなちほー"
console.log(website);              // "http://kemono-friends.jp/"
console.log(tweet);                // "@KemonoKaban がおー! たべちゃうぞー!"
console.log(inReplyToName);        // "かばん"
console.log(inReplyToScreenName);  // "KemonoKaban"

構造を辿りながら書けます。

ただ、これも結構見づらい部類になってしまうかもしれません。どれが変数なのかわからなくなりそう……
そんな時は、プロパティ名をクォートで囲んでしまえばわかりやすいのかもしれません。

分割代入を使った例2
// 変数 json にさっきのJSON文字列が入っているとする
let jsonData = JSON.parse(json);

let {
  name, description,
  'location': region,
  'entities': {
    'url': {
      'urls': [
        {'expanded_url': website},
      ],
    },
  },
  'status': {
    'text': tweet,
    'entities': {
      'user_mentions': [
        {'name': inReplyToName},
      ],
    },
    'in_reply_to_screen_name': inReplyToScreenName,
  },
} = jsonData;

console.log(name);                 // "サーバル"
console.log(description);          // "サーバルキャットのサーバルだよ!"
console.log(region);               // "さばんなちほー"
console.log(website);              // "http://kemono-friends.jp/"
console.log(tweet);                // "@KemonoKaban がおー! たべちゃうぞー!"
console.log(inReplyToName);        // "かばん"
console.log(inReplyToScreenName);  // "KemonoKaban"

応用編

この分割代入いろいろ役に立ちます。

分割代入で少しだけ記述省略

従来の書き方例
var length = list.length;

var first = list[0];
分割代入での書き方例
let {length} = list;

let [first] = list;

Array.prototype.forEachfor...of での使用

let kbyd = [
  ['輿水幸子', 'さっちー'],
  ['姫川友紀', 'ユッキー'],
  ['小早川紗枝', '紗枝はん'],
];

// Array.prototype.forEach
kbyd.forEach(([name, nickname]) => {  // 引数に分割代入を利用する
  console.log(`${name} のニックネームは ${nickname} です`);
});
/*
  "輿水幸子 のニックネームは さっちー です"
  "姫川友紀 のニックネームは ユッキー です"
  "小早川紗枝 のニックネームは 紗枝はん です"
*/

// for...of
for (let [name, nickname] of kbyd) {  // 値を取り出すときに分割代入を利用する
  console.log(`${name} のニックネームは ${nickname} です`);
}
/*
  "輿水幸子 のニックネームは さっちー です"
  "姫川友紀 のニックネームは ユッキー です"
  "小早川紗枝 のニックネームは 紗枝はん です"
*/

配列の中にオブジェクトがあっても同じように書いてやればいけます。
また、ECMAScript 2015から使える MapSet でも for...of を使う際に上記に似た事ができます。(ほぼ同じなので省略)

変数の入れ替え

変数の値を入れ替えたいときにも分割代入が使えます。

従来の書き方
var
  cute = '渋谷凛',
  cool = '本田未央',
  passion = '島村卯月',
  temp;

temp = cute;
cute = passion;
passion = cool;
cool = temp;

console.log(cute);     // "島村卯月"
console.log(cool);     // "渋谷凛"
console.log(passion);  // "本田未央"
従来の書き方
let
  cute = '渋谷凛',
  cool = '本田未央',
  passion = '島村卯月';

[cute, cool, passion] = [passion, cute, cool];

console.log(cute);     // "島村卯月"
console.log(cool);     // "渋谷凛"
console.log(passion);  // "本田未央"

可変プロパティ名への対応

本来ならない方が望ましいですがこんな事もできます。

let underTheDesk = {cute: '佐久間まゆ', cool: '森久保乃々', passion: '星輝子'};

let type = 'cool';

let {[type]: name} = underTheDesk;

console.log(name);  // "森久保乃々"

エラーが出る例

分割代入は varlet で宣言した変数の場合、値を変更することができます。

配列の分割代入
let [cute, cool, passion] = ['早坂美玲', '森久保乃々', '星輝子'];

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

[cute, cool, passion] = ['村松さくら', '大石泉', '土屋亜子'];

console.log(cute);     // "村松さくら"
console.log(cool);     // "大石泉"
console.log(passion);  // "土屋亜子"

// エラーは出ない

しかしオブジェクトの場合、以下の書き方ではエラーになります。

オブジェクトの分割代入
let {cute, cool, passion} = {cute: '早坂美玲', cool: '森久保乃々', passion: '星輝子'};

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

{cute, cool, passion} = {cute: '村松さくら', cool: '大石泉', passion: '土屋亜子'};
// "Uncaught SyntaxError: Unexpected token ="

これは左側の {cute, cool, passion} がオブジェクトリテラルではなくブロックとして扱われてしまうからです。
この場合は丸括弧で囲うと大丈夫です。

オブジェクトの分割代入
let {cute, cool, passion} = {cute: '早坂美玲', cool: '森久保乃々', passion: '星輝子'};

console.log(cute);     // "早坂美玲"
console.log(cool);     // "森久保乃々"
console.log(passion);  // "星輝子"

// 式全体を丸括弧で囲う
({cute, cool, passion} = {cute: '村松さくら', cool: '大石泉', passion: '土屋亜子'});

console.log(cute);     // "村松さくら"
console.log(cool);     // "大石泉"
console.log(passion);  // "土屋亜子"

おわりに

分割代入は結構活躍できそうですが、読みやすさを重視するとちょっとわかりづらい表記もあるのかなと思ったり。