2024年1月27日

デストラクチャリング代入

JavaScriptで最も使用される2つのデータ構造は、ObjectArrayです。

  • オブジェクトを使用すると、キーでデータ項目を格納する単一のエンティティを作成できます。
  • 配列を使用すると、データ項目を順序付けられたリストにまとめることができます。

ただし、これらを関数に渡す場合、すべてが必要とは限りません。関数は特定の要素またはプロパティのみを必要とする場合があります。

デストラクチャリング代入は、配列またはオブジェクトを複数の変数に「展開」できる特別な構文です。場合によっては、これがより便利であるためです。

デストラクチャリングは、多くのパラメーター、デフォルト値などを含む複雑な関数でもうまく機能します。すぐにそれを見てみましょう。

配列のデストラクチャリング

配列を変数にデストラクチャリングする例を次に示します。

// we have an array with a name and surname
let arr = ["John", "Smith"]

// destructuring assignment
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;

alert(firstName); // John
alert(surname);  // Smith

これで、配列のメンバーではなく、変数を使用できます。

splitやその他の配列を返すメソッドと組み合わせると、非常に効果的です。

let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname);  // Smith

ご覧のとおり、構文はシンプルです。ただし、いくつかの独特な詳細があります。より多くの例を見て、より深く理解しましょう。

「デストラクチャリング」は「破壊的」という意味ではありません。

項目を変数にコピーすることによって「デストラクチャリング」するため、「デストラクチャリング代入」と呼ばれます。ただし、配列自体は変更されません。

これは単に、次のように記述するより短い方法です。

// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
カンマを使用して要素を無視する

配列の不要な要素は、余分なカンマを使用して削除することもできます。

// second element is not needed
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

上記のコードでは、配列の2番目の要素はスキップされ、3番目の要素はtitleに割り当てられ、残りの配列要素もスキップされます(それらに対応する変数がないため)。

右辺の任意の反復可能オブジェクトで動作する

実際、配列だけでなく、任意の反復可能オブジェクトで使用できます。

let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);

これは、内部的にデストラクチャリング代入が右辺の値を反復処理することによって機能するためです。これは、=の右側の値に対してfor..ofを呼び出し、値を割り当てるための構文シュガーの一種です。

左辺の任意の場所に代入する

左辺には、任意の「代入可能」なものを使用できます。

たとえば、オブジェクトのプロパティです。

let user = {};
[user.name, user.surname] = "John Smith".split(' ');

alert(user.name); // John
alert(user.surname); // Smith
.entries()を使用したループ

前の章では、Object.entries(obj)メソッドについて説明しました。

デストラクチャリングと組み合わせて、オブジェクトのキーと値をループ処理できます。

let user = {
  name: "John",
  age: 30
};

// loop over the keys-and-values
for (let [key, value] of Object.entries(user)) {
  alert(`${key}:${value}`); // name:John, then age:30
}

Mapの場合、反復可能であるため、同様のコードはよりシンプルです。

let user = new Map();
user.set("name", "John");
user.set("age", "30");

// Map iterates as [key, value] pairs, very convenient for destructuring
for (let [key, value] of user) {
  alert(`${key}:${value}`); // name:John, then age:30
}
変数の入れ替えトリック

デストラクチャリング代入を使用して2つの変数の値を入れ替えるためのよく知られたトリックがあります。

let guest = "Jane";
let admin = "Pete";

// Let's swap the values: make guest=Pete, admin=Jane
[guest, admin] = [admin, guest];

alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)

ここでは、2つの変数のテンポラリ配列を作成し、それをすぐに入れ替えた順序でデストラクチャリングします。

これにより、2つ以上の変数を入れ替えることができます。

rest ‘…’

通常、配列が左側のリストよりも長い場合、「余分な」アイテムは省略されます。

たとえば、ここでは2つのアイテムのみが取得され、残りは無視されます。

let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar
// Further items aren't assigned anywhere

それに続くものをすべて収集する場合、3つのドット"..."を使用して「残りの部分」を取得するパラメーターをもう1つ追加できます。

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

// rest is an array of items, starting from the 3rd one
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

restの値は、残りの配列要素の配列です。

restの代わりに他の変数名を使用できます。3つのドットの前に配置し、デストラクチャリング代入の最後に配置するようにしてください。

let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// now titles = ["Consul", "of the Roman Republic"]

デフォルト値

配列が左側の変数のリストよりも短い場合、エラーは発生しません。存在しない値はundefinedと見なされます。

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

不足している値を置き換える「デフォルト」値が必要な場合は、=を使用して指定できます。

// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (from array)
alert(surname); // Anonymous (default used)

デフォルト値は、より複雑な式または関数呼び出しにすることができます。値が提供されていない場合にのみ評価されます。

たとえば、ここでは2つのデフォルトにprompt関数を使用しています。

// runs only prompt for surname
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];

alert(name);    // Julius (from array)
alert(surname); // whatever prompt gets

注:promptは、欠けている値(surname)に対してのみ実行されます。

オブジェクトのデストラクチャリング

デストラクチャリング代入は、オブジェクトでも機能します。

基本的な構文は次のとおりです。

let {var1, var2} = {var1:, var2:}

右側に、変数に分割したい既存のオブジェクトが必要です。左側は、対応するプロパティのオブジェクトのような「パターン」を含んでいます。最も単純なケースでは、{...}内の変数名のリストです。

たとえば

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

options.titleoptions.widthoptions.heightのプロパティは、対応する変数に割り当てられます。

順序は関係ありません。これも機能します。

// changed the order in let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }

左側のパターンはより複雑にすることができ、プロパティと変数の間のマッピングを指定します。

プロパティを変数に別の名前で割り当てる場合(たとえば、options.widthを変数wに入れる場合)、コロンを使用して変数名を設定できます。

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

コロンは「何が:どこにいくか」を示します。上記の例では、widthプロパティはwに、heightプロパティはhに、titleは同じ名前に割り当てられます。

可能性のある欠落プロパティには、次のように"="を使用してデフォルト値を設定できます。

let options = {
  title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

配列や関数のパラメーターと同様に、デフォルト値は任意の式または関数呼び出しにすることができます。値が提供されていない場合にのみ評価されます。

以下のコードでは、promptwidthを要求しますが、titleは要求しません。

let options = {
  title: "Menu"
};

let {width = prompt("width?"), title = prompt("title?")} = options;

alert(title);  // Menu
alert(width);  // (whatever the result of prompt is)

コロンと等号の両方を使用することもできます。

let options = {
  title: "Menu"
};

let {width: w = 100, height: h = 200, title} = options;

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

多くのプロパティを持つ複雑なオブジェクトがある場合、必要なものだけを抽出できます。

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// only extract title as a variable
let { title } = options;

alert(title); // Menu

restパターン“…”

オブジェクトに変数よりも多くのプロパティがある場合はどうなりますか?いくつかを取得して、「残りの部分」をどこかに割り当てることができますか?

配列で行ったように、restパターンを使用できます。一部の古いブラウザー(IE)ではサポートされていません(Babelを使用してポリフィルします)が、最新のブラウザーでは機能します。

次のようになります。

let options = {
  title: "Menu",
  height: 200,
  width: 100
};

// title = property named title
// rest = object with the rest of properties
let {title, ...rest} = options;

// now title="Menu", rest={height: 200, width: 100}
alert(rest.height);  // 200
alert(rest.width);   // 100
letがない場合の注意点

上記の例では、変数は代入時に直接宣言されました:let {…} = {…}。もちろん、letなしで既存の変数を使用することもできます。しかし、注意点があります。

これは機能しません。

let title, width, height;

// error in this line
{title, width, height} = {title: "Menu", width: 200, height: 100};

問題は、JavaScriptがメインのコードフロー(別の式内ではない)にある{...}をコードブロックとして扱うことです。このようなコードブロックは、次のようにステートメントをグループ化するために使用できます。

{
  // a code block
  let message = "Hello";
  // ...
  alert( message );
}

そのため、ここではJavaScriptはコードブロックがあると想定しているため、エラーが発生します。代わりにデストラクチャリングが必要です。

JavaScriptにそれがコードブロックではないことを示すには、式を括弧(...)で囲みます。

let title, width, height;

// okay now
({title, width, height} = {title: "Menu", width: 200, height: 100});

alert( title ); // Menu

ネストされたデストラクチャリング

オブジェクトまたは配列に他のネストされたオブジェクトや配列が含まれている場合、より複雑な左辺のパターンを使用して、より深い部分を抽出できます。

以下のコードでは、optionssizeプロパティに別のオブジェクトを、itemsプロパティに配列を持っています。代入の左側のパターンは、それらから値を抽出するための同じ構造を持っています。

let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Cake", "Donut"],
  extra: true
};

// destructuring assignment split in multiple lines for clarity
let {
  size: { // put size here
    width,
    height
  },
  items: [item1, item2], // assign items here
  title = "Menu" // not present in the object (default value is used)
} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
alert(item1);  // Cake
alert(item2);  // Donut

左側にないextraを除く、optionsオブジェクトのすべてのプロパティは、対応する変数に割り当てられます。

最後に、デフォルト値からwidthheightitem1item2titleを取得します。

sizeitemsに対応する変数がないことに注意してください。代わりにその内容を取得しているためです。

スマートな関数パラメーター

関数が多数のパラメーターを持ち、そのほとんどがオプションである場合があります。これは、ユーザーインターフェースで特に当てはまります。メニューを作成する関数を考えてみてください。幅、高さ、タイトル、アイテムリストなどがある可能性があります。

このような関数を記述する悪い方法を次に示します。

function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
  // ...
}

実際には、引数の順序を覚える方法が問題です。通常、IDEは、特にコードが適切に文書化されている場合は、私たちを支援しようとしますが、それでも…もう1つの問題は、ほとんどのパラメーターがデフォルトで問題ない場合に関数を呼び出す方法です。

このような感じですか?

// undefined where default values are fine
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])

これは見苦しく、より多くのパラメーターを扱うと読みづらくなります。

デストラクチャリングが役に立ちます!

パラメーターをオブジェクトとして渡すことができ、関数はそれらをすぐに変数にデストラクチャリングします。

// we pass object to function
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
  // title, items – taken from options,
  // width, height – defaults used
  alert( `${title} ${width} ${height}` ); // My Menu 200 100
  alert( items ); // Item1, Item2
}

showMenu(options);

ネストされたオブジェクトとコロンマッピングを使用した、より複雑なデストラクチャリングを使用することもできます。

let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

function showMenu({
  title = "Untitled",
  width: w = 100,  // width goes to w
  height: h = 200, // height goes to h
  items: [item1, item2] // items first element goes to item1, second to item2
}) {
  alert( `${title} ${w} ${h}` ); // My Menu 100 200
  alert( item1 ); // Item1
  alert( item2 ); // Item2
}

showMenu(options);

完全な構文は、デストラクチャリング代入の場合と同じです。

function({
  incomingProperty: varName = defaultValue
  ...
})

次に、パラメーターのオブジェクトには、incomingPropertyプロパティの変数varNameがあり、デフォルトではdefaultValueが使用されます。

このようなデストラクチャリングは、showMenu()に引数があることを前提としています。すべての値をデフォルトで使用する場合は、空のオブジェクトを指定する必要があります。

showMenu({}); // ok, all values are default

showMenu(); // this would give an error

パラメーターのオブジェクト全体に{}をデフォルト値にすることで、これを修正できます。

function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
  alert( `${title} ${width} ${height}` );
}

showMenu(); // Menu 100 200

上記のコードでは、引数のオブジェクト全体がデフォルトで{}であるため、常にデストラクチャリングできるものがあります。

まとめ

  • デストラクチャリング代入により、オブジェクトまたは配列を多くの変数に瞬時にマッピングできます。

  • 完全なオブジェクト構文

    let {prop : varName = defaultValue, ...rest} = object

    これは、propプロパティを変数varNameに入れる必要があり、そのようなプロパティが存在しない場合は、default値を使用する必要があることを意味します。

    マッピングがないオブジェクトプロパティは、restオブジェクトにコピーされます。

  • 完全な配列構文

    let [item1 = defaultValue, item2, ...rest] = array

    最初のアイテムはitem1に、2番目のアイテムはitem2に、残りはすべて配列restになります。

  • ネストされた配列やオブジェクトからデータを取り出すことができます。そのためには、左辺の構造と右辺の構造が同じでなければなりません。

課題

重要度: 5

オブジェクトがあります

let user = {
  name: "John",
  years: 30
};

以下の値を読み取るデストラクチャリング代入を書いてください。

  • name プロパティを name 変数に。
  • years プロパティを age 変数に。
  • isAdmin プロパティを isAdmin 変数に(存在しない場合は false)。

代入後の値の例を以下に示します。

let user = { name: "John", years: 30 };

// your code to the left side:
// ... = user

alert( name ); // John
alert( age ); // 30
alert( isAdmin ); // false
let user = {
  name: "John",
  years: 30
};

let {name, years: age, isAdmin = false} = user;

alert( name ); // John
alert( age ); // 30
alert( isAdmin ); // false
重要度: 5

salaries オブジェクトがあります。

let salaries = {
  "John": 100,
  "Pete": 300,
  "Mary": 250
};

最高給与の人の名前を返す topSalary(salaries) 関数を作成してください。

  • salaries が空の場合は、null を返します。
  • 最高給与の人が複数いる場合は、そのうちの一人(誰か一人)の名前を返します。

P.S. Object.entries とデストラクチャリングを使用して、キーと値のペアを反復処理してください。

テスト付きのサンドボックスを開きます。

function topSalary(salaries) {

  let maxSalary = 0;
  let maxName = null;

  for(const [name, salary] of Object.entries(salaries)) {
    if (maxSalary < salary) {
      maxSalary = salary;
      maxName = name;
    }
  }

  return maxName;
}

テスト付きの解答をサンドボックスで開きます。

チュートリアルマップ