JavaScriptの基礎 - 分割代入

提供: MochiuWiki : SUSE, EC, PCB

概要

分割代入 (Destructuring Assignment) は、配列やオブジェクトから値を取り出して、個別の変数に代入するための構文である。
ES2015 (ES6) で導入されたこの構文を使用することで、プロパティアクセスや添字アクセスを繰り返す冗長なコードを簡潔に記述できる。

オブジェクトの分割代入では波括弧 {} を使用し、プロパティ名と同名の変数に値を取り出す。
配列の分割代入では角括弧 [] を使用し、インデックス順に値を取り出す。

分割代入は以下の場面で特に有効に機能する。

  • 関数の引数でオブジェクトを受け取る時に、必要なプロパティのみを取り出す。
  • 関数の戻り値が複数の値を持つ場合に、それぞれを個別の変数に代入する。
  • ネストしたデータ構造から特定のプロパティを取り出す。
  • 変数の値を交換する。
  • Reactコンポーネントのpropsを受け取る。


デフォルト値の指定、別名 (リネーム) への代入、残余パターン (...rest) との組み合わせ等、柔軟な構文拡張が用意されており、モダンJavaScript開発において広く使用される重要な機能である。


オブジェクトの分割代入

オブジェクトの分割代入は、波括弧 {} を左辺に記述し、オブジェクトのプロパティ名と同名の変数に値を取り出す構文である。

基本構文

オブジェクトの分割代入の基本的な使い方を以下に示す。

 // 従来の書き方
 const obj = { a: 1, b: 2 };
 const a = obj.a;
 const b = obj.b;
 
 // 分割代入を使用した書き方
 const { a, b } = { a: 1, b: 2 };
 console.log(a);  // 1
 console.log(b);  // 2


既存変数への代入を行う場合は、文の先頭に波括弧を配置するとブロック文として解釈されるため、式全体を丸括弧で囲む必要がある。

 let a, b;
 
 // 丸括弧で囲まないとブロック文として解釈されてしまう
 ({ a, b } = { a: 1, b: 2 });
 
 console.log(a);  // 1
 console.log(b);  // 2


算出プロパティ名を使用した分割代入も可能である。

 // 算出プロパティ名での分割代入
 const key = "z";
 const { [key]: foo } = { z: "bar" };
 console.log(foo);  // "bar"
 
 // ハイフンを含む等、無効な識別子名のプロパティも取り出せる
 const { "fizz-buzz": fizzBuzz } = { "fizz-buzz": true };
 console.log(fizzBuzz);  // true


デフォルト値

分割代入では、変数に代入される値が undefined の場合に使用するデフォルト値を指定できる。

 // プロパティが存在しない場合にデフォルト値が使用される
 const { a, b = 10 } = { a: 1 };
 console.log(a);  // 1
 console.log(b);  // 10 (デフォルト値)
 
 // プロパティが存在する場合はデフォルト値は無視される
 const { x = 5, y = 7 } = { x: 1 };
 console.log(x);  // 1 (デフォルト値は使用されない)
 console.log(y);  // 7 (デフォルト値)
 
 // nullの場合はデフォルト値は適用されない (undefinedの場合のみ適用される)
 const { value = "デフォルト" } = { value: null };
 console.log(value);  // null


リネーム (別名への代入)

コロン : を使用することにより、プロパティを別名の変数に代入できる。
これをリネーム (別名への代入) と呼ぶ。

 // プロパティ名 : 変数名 の形式でリネームする
 const { name: userName } = { name: "Alice" };
 console.log(userName);  // "Alice"
 // console.log(name);   // エラー: nameは未定義
 
 // リネームとデフォルト値を組み合わせることができる
 const { name: displayName = "Guest" } = {};
 console.log(displayName);  // "Guest"
 
 // 複数のプロパティを同時にリネームする
 const { firstName: first, lastName: last } = { firstName: "John", lastName: "Doe" };
 console.log(first);  // "John"
 console.log(last);   // "Doe"



配列の分割代入

配列の分割代入は、角括弧 [] を左辺に記述し、インデックス順に値を変数へ取り出す構文である。

基本構文

配列の分割代入の基本的な使い方を以下に示す。

 // 従来の書き方
 const arr = [1, 2, 3];
 const first = arr[0];
 const second = arr[1];
 
 // 分割代入を使用した書き方
 const [a, b] = [1, 2];
 console.log(a);  // 1
 console.log(b);  // 2
 
 // 3つ以上の要素も同様に取り出せる
 const [x, y, z] = [10, 20, 30];
 console.log(x, y, z);  // 10 20 30


要素のスキップ

カンマ , を連続で記述することで、特定のインデックスの要素をスキップできる。

 // 第1要素と第3要素をスキップして、第2要素と第4要素のみ取り出す
 const [, second, , fourth] = [1, 2, 3, 4];
 console.log(second);  // 2
 console.log(fourth);  // 4
 
 // 先頭の複数要素をスキップする場合
 const [, , third] = [1, 2, 3];
 console.log(third);   // 3


デフォルト値

配列の分割代入でも、値が undefined の場合に使用するデフォルト値を指定できる。

 // 配列の要素数が変数の数より少ない場合にデフォルト値が使用される
 const [a = 10, b = 20] = [1];
 console.log(a);  // 1  (配列の値)
 console.log(b);  // 20 (デフォルト値)
 
 // 全ての変数にデフォルト値を指定する例
 const [p = 100, q = 200, r = 300] = [1, 2];
 console.log(p);  // 1
 console.log(q);  // 2
 console.log(r);  // 300 (デフォルト値)



ネストした分割代入

ネストしたオブジェクトや配列に対しても、同様のパターンを入れ子にして分割代入を適用できる。

ネストしたオブジェクトの分割代入を以下に示す。

 // ネストしたオブジェクトから値を取り出す
 const { name, address: { city } } = {
    name: "Alice",
    address: { city: "Tokyo" }
 };
 
 console.log(name);  // "Alice"
 console.log(city);  // "Tokyo"
 // console.log(address);  // エラー: addressは変数として宣言されていない
 
 // 深くネストした構造にも適用できる
 const { a: { b: { c } } } = { a: { b: { c: 42 } } };
 console.log(c);  // 42


ネストした配列の分割代入を以下に示す。

 // ネストした配列から値を取り出す
 const [[a, b], [c, d]] = [[1, 2], [3, 4]];
 console.log(a, b, c, d);  // 1 2 3 4
 
 // 配列とオブジェクトを組み合わせた分割代入
 const [{ name: first }, { name: second }] = [
    { name: "Alice" },
    { name: "Bob" }
 ];
 console.log(first);   // "Alice"
 console.log(second);  // "Bob"


for-of ループと組み合わせることで、配列内のオブジェクトを簡潔に処理できる。

 const people = [
    { name: "Alice", family: { father: "Bob" } },
    { name: "Carol", family: { father: "Dave" } }
 ];
 
 for (const { name: n, family: { father: f } } of people) {
    console.log(`Name: ${n}, Father: ${f}`);
 }
 // "Name: Alice, Father: Bob"
 // "Name: Carol, Father: Dave"



残余パターン (...rest)

分割代入において、取り出した残りの要素をまとめて受け取る構文が残余パターン (...rest) である。

スプレッド構文と同じ ... 記号を使用するが、分割代入の文脈では「残りをまとめる」意味を持つ。

オブジェクトの残余パターン

オブジェクトの分割代入で残余パターンを使用すると、取り出していない残りのプロパティをオブジェクトとして収集できる。

 // 残余パターンで残りのプロパティをまとめて受け取る
 const { a, ...others } = { a: 1, b: 2, c: 3 };
 console.log(a);       // 1
 console.log(others);  // { b: 2, c: 3 }
 
 // 複数のプロパティを取り出した後の残余
 const { x, y, ...rest } = { x: 1, y: 2, z: 3, w: 4 };
 console.log(x);     // 1
 console.log(y);     // 2
 console.log(rest);  // { z: 3, w: 4 }


残余パターンは、特定のプロパティを除外した新しいオブジェクトを生成するパターンとしても活用される。

 // 機密情報を除外してサーフェイス用オブジェクトを生成するパターン
 const user = { id: 1, name: "Alice", email: "alice@example.com", password: "secret" };
 
 const { password, ...safeUser } = user;
 console.log(safeUser);
 // { id: 1, name: "Alice", email: "alice@example.com" }
 // passwordは除外されている


配列の残余パターン

配列の分割代入で残余パターンを使用すると、残りの要素を配列として収集できる。

 // 最初の要素を取り出して、残りを配列として受け取る
 const [first, ...rest] = [1, 2, 3];
 console.log(first);  // 1
 console.log(rest);   // [2, 3]
 
 // 複数の要素を取り出した後の残余
 const [head, second, ...tail] = [1, 2, 3, 4, 5];
 console.log(head);    // 1
 console.log(second);  // 2
 console.log(tail);    // [3, 4, 5]
 
 // 残余パターンは必ず最後に記述する必要がある
 // const [...rest, last] = [1, 2, 3];  // SyntaxError



関数引数での分割代入

関数の仮引数に分割代入を直接記述することで、渡されたオブジェクトや配列から必要な値を取り出しながら受け取ることができる。

オブジェクト引数の分割代入

関数の引数にオブジェクトを受け取る際に分割代入を使用すると、必要なプロパティのみを取り出して受け取ることができる。
引数の順序を気にする必要がなく、コードの意図が明確になる。

 // 従来の書き方
 function greetOld(options) {
    const name = options.name;
    const age = options.age || 0;
    console.log(`${name} (${age}歳)`);
 }
 
 // 分割代入を使用した書き方
 function greet({ name, age = 0 }) {
    console.log(`${name} (${age}歳)`);
 }
 
 greet({ name: "Alice", age: 30 });  // "Alice (30歳)"
 greet({ name: "Bob" });             // "Bob (0歳)" (デフォルト値が使用される)


リネームとデフォルト値を組み合わせた高度なパターンも使用できる。

 // リネームとデフォルト値を組み合わせた引数の分割代入
 function createUser({ name: userName = "Anonymous", role: userRole = "user" } = {}) {
    console.log(`ユーザ名: ${userName}, 権限: ${userRole}`);
 }
 
 createUser({ name: "Alice", role: "admin" });  // "ユーザ名: Alice, 権限: admin"
 createUser({ name: "Bob" });                   // "ユーザ名: Bob, 権限: user"
 createUser();                                  // "ユーザ名: Anonymous, 権限: user"
 
 // 複数の引数パターンを組み合わせる
 function myFunc({ a, b }, c = 1, ...rest) {
    console.log(a, b, c, rest);
 }
 
 myFunc({ a: 10, b: 20 }, 5, "x", "y");
 // 10 20 5 ["x", "y"]


Reactのprops受け取りパターン

Reactのコンポーネントでは、props オブジェクトを引数で受け取る時に分割代入を使用することが一般的である。
必要なpropsを明示的に列挙することで、コンポーネントのインターフェースが明確になる。

 // propsオブジェクトをそのまま受け取る従来の書き方
 function UserCardOld(props) {
    return <div>{props.name} - {props.email}</div>;
 }
 
 // 分割代入でpropsを受け取る書き方 (推奨)
 function UserCard({ name, email, role = "user" }) {
    return (
       <div>
          <h2>{name}</h2>
          <p>{email}</p>
          <span>{role}</span>
       </div>
    );
 }
 
 // 使用例
 // <UserCard name="Alice" email="alice@example.com" role="admin" />
 // <UserCard name="Bob" email="bob@example.com" />  role は "user" になる


残余パターンと組み合わせることで、特定のpropsを取り出しつつ残りのpropsを子コンポーネントに転送するパターンも実現できる。

 // 特定のpropsを取り出して、残りを子要素に転送するパターン
 function Button({ label, onClick, ...rest }) {
    return (
       <button onClick={onClick} {...rest}>
          {label}
       </button>
    );
 }
 
 // <Button label="送信" onClick={handleClick} className="btn-primary" disabled={false} />
 // labelとonClickは取り出され、残りのclassNameとdisabledはbuttonに渡される



実用的なパターン

分割代入は単なる変数代入の省略記法にとどまらず、様々な実用的なパターンで活用される。

変数の交換

従来は一時変数が必要だった2変数の値の交換を、配列の分割代入で簡潔に記述できる。

 // 従来の書き方: 一時変数が必要だった
 let a = 1, b = 2;
 const temp = a;
 a = b;
 b = temp;
 console.log(a, b);  // 2 1
 
 // 分割代入を使用した書き方: 一時変数が不要
 let x = 1, y = 2;
 [x, y] = [y, x];
 console.log(x, y);  // 2 1
 
 // 3変数の交換も可能
 let p = 1, q = 2, r = 3;
 [p, q, r] = [q, r, p];
 console.log(p, q, r);  // 2 3 1


関数の複数戻り値

JavaScriptの関数は単一の値しか返せないが、配列またはオブジェクトで複数の値を返し、分割代入で受け取るパターンが広く使用される。

 // 配列で複数の値を返す関数
 function getMinMax(arr) {
    return [Math.min(...arr), Math.max(...arr)];
 }
 
 const [min, max] = getMinMax([3, 1, 4, 1, 5, 9, 2, 6]);
 console.log(min);  // 1
 console.log(max);  // 9
 
 // オブジェクトで複数の値を返す関数 (プロパティ名で受け取れるため順序が問われない)
 function parseDate(dateStr) {
    const [year, month, day] = dateStr.split("-");
    return { year: Number(year), month: Number(month), day: Number(day) };
 }
 
 const { year, month, day } = parseDate("2025-03-15");
 console.log(year, month, day);  // 2025 3 15


Object.entriesfor-of ループを組み合わせた分割代入は、オブジェクトのキーと値を同時に処理する時に頻繁に使用される。

 const scores = { Alice: 95, Bob: 87, Carol: 92 };
 
 // Object.entriesとfor-ofで分割代入を使用する
 for (const [key, value] of Object.entries(scores)) {
    console.log(`${key}: ${value}点`);
 }
 // "Alice: 95点"
 // "Bob:   87点"
 // "Carol: 92点"
 
 // Mapオブジェクトのイテレーションでも同様に使用できる
 const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
 for (const [key, value] of map) {
    console.log(`${key}: ${value}`);
 }
 // "a: 1"
 // "b: 2"
 // "c: 3"



関連情報