LINDORのAdvent Calendar(本物)の21日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 21日目

配列を分解しながら要素ごとに変数へ代入することができます。

const n = arr[0];
const m = arr[1];

↓

const [n, m] = arr;

使える場所

  • 変数の初期化(let, const 宣言)、代入
  • 関数引数

できること

  • 必要なものだけ受け取って初期化、代入
  • 残余 ...
  • 初期値 =

使い方

配列を分解して変数を作る

const arr = [11, 22, 33, 44, 55];
const [a, b, c, d, e] = arr;
console.log(a, b, c, d, e);

要素の省略

先頭や途中のものを飛ばす場合、変数を省略してカンマ , だけ置きます。

const [ , b, , d, e] = arr;

末尾から飛ばす場合は何も書かなくて良い。

const [ , b, ,] = arr;

なんなら全部飛ばして [,,,] = arr みたいにも文法的には書けます。書くことないだろうけど。

入れ子

2次元配列の場合、受け取り側も2次元配列で表現することで、子配列の要素を直接得られます。

const positions = [
  [11, 22],
  [33, 44],
];

const [
  [x1, y1],
  [x2, y2]
] = positions;
console.log(x1, y1, x2, y2);
// 11 22 33 44

もちろん2次元に限らず、N次元でいくらでも入れ子にできます。

オブジェクトの記法と組み合わせることも可能。

残余

... を使って「残り全部」を受け取れます。

const [a, ...rest] = arr;

これも入れ子にしたり、オブジェクトの記法と組み合わせたりすることができます。

const arr = [11, 22, 33];
const [a, ...{ length }] = arr;
console.log(a, length);
// 11 2

変数を使う方の場面では逆にばらばらの値へ展開することができます。

初期値

得られた値が undefined のとき、 = で指定した初期値が変数に格納されます。基準は値の有無ではなく undefined かどうかです。

const arr = [11, undefined];
const [a = -11, b = -22, c = -33] = arr;
console.log(a, b, c);
// 11 -22 -33

入れ子と組み合わせるのもあり。

利用可能なもの

配列以外でも、反復可能なものなら何でもいける。

const set = new Set([11, 22]);
const [a, b] = set;
console.log(a, b);
// 11 22
function* gen () {
  yield 11;
  yield 22;
  yield 33;
}

const [a, b, c] = gen();
console.log(a, b, c);
// 11 22 33

配列風オブジェクトはだめ

反復可能ではないので。

const obj = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
};
const [a, b, c] = obj;
console.log(a, b, c);

TypeError: obj is not iterable

関数引数も分解や残余を使える

[a, b, c] の代わりに (a, b, c) 的な雰囲気で、同じように ... で「残り」を得たり、 [] で分解して受け取ることができます。

function* gen () {
  yield 11;
  yield 22;
  yield 33;
}

const f = ([a, ...rest]) => console.log(a, rest);
f(gen());
// 11 [ 22, 33 ]

arguments オブジェクトがあるし、 (a, b, ...rest) を [a, b, ...rest] = arguments みたいに考えれば妥当だよね。

利用例

正規表現でURLを分解

// 実務でご利用の際は `match()` の結果が `null` になり得る点をお忘れなく

const url = 'https://ginpei.info/path/to/file';

const matched = url.match(/(https?:)\/\/([\w.-]+)(.*)/);
const [ , protocol, host, path] = matched;
console.log('protocol:', protocol);
console.log('host:', host);
console.log('path:', path);
// protocol: https:
// host: ginpei.info
// path: /path/to/file

URLのパラメーターを分解

const search = '?id=123&mode=fine'; // location.searchみたいな
const params = search
  .slice(1) // 冒頭 "?" を飛ばす
  .split('&')
  .reduce((map, pair) => {
    const [name, value] = pair.split('=');
    map[name] = value;
    return map;
  }, {});
console.log(params);
// => { id: '123', mode: 'fine' }

実際は items[]=a&items[]=b みたいな重複したものにも対応が必要かもね。

さらっと reduce() 使ったけど、何でもできる配列の最強メソッドです。

Object.entries() で

key-value組を分解するのにも便利。

const colors = {
  apple: 'red',
  banana: 'yellow',
  orange: 'orange',
};

Object.entries(colors).forEach(([name, color]) => {
  console.log(`${name} is ${color}`);
});

for-of でも

of の左側でも使えます。

const colors = new Map();
colors.set('apple', 'red');
colors.set('banana', 'yellow');
colors.set('orange', 'orange');

for (const [name, color] of colors) {
  console.log(`${name} is ${color}`);
}

Promise.all() の結果で

複数のAPIを並列に fetch() した結果とか。

const [a, b] = await Promise.all([
  new Promise((resolve) => resolve('Hello')),
  new Promise((resolve) => resolve('World!')),
]);
console.log(a, b); // => Hello World!

コマンドを受け付ける

jQuery UIのやつみたいに、第1引数にコマンド名、以下コマンドに応じて0個以上のパラメーター、みたいな。

function exec (command, ...params) {
  switch (command) {
    // なんかする
  }
}

exec('start');
exec('say', 'Hello!');
exec('move', 10, 22);

残余引数で受けるより、 params をオブジェクトでまるっともらう方が良さそうな気もする。

その他

残余引数と関数引数の数

関数オブジェクトは length プロパティを持っていて、引数の数が格納されてます。残余引数の場合はそれに数えられません。

先のこの例↓だと、 f.length は 0 になります。

const f = (...args) => console.log(args);

ちなみに手元のEdge (EdgeHTML 17) だと 1 になりました。へえ。

残余引数の分解

結合してからの分解。なんだこれ。

const f = (...[a, b]) => console.log(a, b);

前述の通り ... を使うと関数引数の数 f.length に反映されないので、こっそり受け付けたいときにこの組み合わせが便利ですね。嘘です、たぶんそんなことする理由ない。

なお手元のEdge (EdgeHTML 17) だと動きませんでした。(関数引数じゃなくて変数の方は動く。)

Object doesn't support property or method 'Symbol.iterator'

オブジェクトの分割代入

同じようなもんです。 { ...rest } もある。

おしまい

ないならないでも書けるんだけど、使えると短くかけてすごく便利。

関連

参考