ねこものがたり

いちにちいっぽ

JSで関数の引数として渡す分割代入を理解する

背景

最近のモダンJSをやっているとこのような関数をよく見たり書いたりします。

function doSomething({ id, name }) { 
  ...
}

この時の引数になっている{ id, name }の部分について、「JSONでキーが一致したもののバリューをとってくる便利記法なんだろうな」というのを、既存コードから感覚で認識するだけにとどまっていました。 この認識は間違いではなかったのですが、このままでは「自分なりに良いとフロントエンドのコードを書く」「意図を持って実装する」ということはできないと思ったので、これを自分なりに説明できるくらいにはもう少しきちんと理解しておこうと思いました。

分割代入

参考資料

MDNを参考にしました。

Destructuring assignment - JavaScript | MDN

分割代入とは

構成する要素を一度分解して個別の変数に代入することができる記法のこと。 配列の要素か、オブジェクトのプロパティに対して使うことができる。

基本的な使用例

まずは基本を押さえてみました。

Arrayの場合

const x = [1, 2, 3, 4, 5];
const [a, b] = x; // 宣言する変数はa, bからなる配列
console.log(a); // 1
console.log(b); // 2

Objectの場合

基本

const obj = { a: 1, b: 2 };
const { a, b } = obj; 宣言する変数はa, bからなるオブジェクト
console.log(a); // 1 キーが一致したもののバリューが代入される
console.log(b); // 2

キーとは別の変数名にしたい場合

aというキーの値をdという名前で代入したい例。

const obj = { a: 1, b: 2 };
const { a: d, b } = obj;
console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // 2
console.log(d); // 1

ネストした値を取りたい場合

objというオブジェクトがbというキーを持ったオブジェクトを持っていてネストしている。 ここではネストしていないaとネストしている中のmを同名の変数に代入する例。

const obj = { a: 1, b: {m: 2, n: 3}};
const {a, b: {m}} = obj
console.log(a); // 1
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(m); //2

Array, Objectで共通の記法

デフォルト値の指定

=を使ってデフォルト値を指定できます。デフォルト値が適用されるのはundefinedの時のみで、nullの場合はnullになります。

// デフォルト値指定している変数と対応する要素に値が入っている場合
array = [1, 2];
const [a, b] = array;
console.log(a); // 1
console.log(b); // 2

// デフォルト値指定している変数と対応する要素が undefined の場合
array = [undefined, 2]
const [a = 3, b] = array;
console.log(a); // 3
console.log(b); // 2

// デフォルト値指定している変数と対応する要素が null の場合
array = [null, 2]
const [a = 3, b] = array;
console.log(a); // null
console.log(b); // 2

残余値の代入

...を使って表現できます。

const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(a); // 1
console.log(others); // {b: 2, c: 3}

複数個の変数宣言の場合は、末尾に記述します。位置がおかしいとエラーで教えてくれます。

const { a, ...others, d } = { a: 1, b: 2, c: 3, d: 4 }; // Uncaught SyntaxError: Rest element must be last element

ignoreする

, ,と変数宣言の時に何も記述しないことで代入する要素やプロパティをスキップすることができます

array = [ 1, 2, 3];
const [x, ,z] = array;
console.log(x); // 1
console.log(z); // 3

関数で分割代入を利用してオブジェクトを渡す場合の使用例

ここからがこの記事で書きたかった本題です。これまでの基本を押さえていれば、あとはそれと同じようにやるだけ、あるいは組み合わせたりと応用もできます。

const user = {
  id: 42,
  displayName: 'jdoe',
  fullName: {
    firstName: 'John',
    lastName: 'Doe',
  },
};

function userId({ id }) { 
  return id;
}

// userの中にあるキー`id`と一致したバリューが返ってくる
console.log(userId(user)); // 42

// 関数の中ではuserの中にあるキー`displayName`を`dname`としている
function userDisplayName({ displayName: dname }) {
  // return displayName とするとdisplayName is not definedになる
  return dname;
}

関数の引数に分割代入をする際、MDNには、デフォルト値を適切に設定することで不具合が起きないようにする心がけについても触れられていました。

You could have also written the function without that default. However, if you leave out that default value, the function will look for at least one argument to be supplied when invoked, whereas in its current form, you can call drawChart() without supplying any parameters. Otherwise, you need to at least supply an empty object literal.

その他

「JSで関数の引数として渡す分割代入を理解する」の一歩としては結構捗ったと思うのでこの記事ではここまでにしますが、該当のMDNのページ Destructuring assignment - JavaScript | MDNにはもっとたくさんの情報があって、「それはそう」と流してしまっているような点も説明があるなどすごく良いなとおもいました。 随時参照していこうと思います。

手を動かしていて気づいたこと

Rubyの感覚で変数代入を書いてエラーになるというミスを数えきれないほどやってしまって、Rubyの手馴染みの良さみたいなものを実感しました。

# Rubyの場合
array = [1, 2, 3]
a, b = array
p a
#=> 1
p b
#=> 2


// JSの場合
array = [1, 2, 3]
const a, b = array //  Missing initializer in const declaration
const [a, b] = array // これは成功する。arrayの分割代入の場合左辺も[]が必要。オブジェクトの場合は {} が必要。

最後に

JSのソースコードってどうやって見るのか知らないなーと思ったので、わかっておきます。