基礎から学ぶJavaScript入門 90日コース | JavaScriptでデータを扱う - Day 23:スプレッド構文

90日で身につけるJavaScript
スポンサーリンク
スポンサーリンク
  1. Day 23:スプレッド構文(前半)
  2. スプレッド構文(…)とは何か
    1. 「中身を“展開する”ための記号」
  3. 配列のコピー:元の配列を壊さないための基本テクニック
    1. なぜ「コピー」が必要なのか
    2. スプレッド構文で「安全なコピー」を作る
  4. 配列の結合:複数の配列を1つにまとめる
    1. 2つの配列をくっつける基本形
    2. 途中に別の値を挟むこともできる
  5. オブジェクトのコピー:元のオブジェクトを壊さない
    1. 危険なパターン:オブジェクトをそのまま代入する
    2. スプレッド構文でオブジェクトをコピーする
  6. オブジェクトのコピー+一部変更:新しい状態を作る
    1. 「元のデータ+変更部分」で新しいオブジェクトを作る
  7. 実践:データを変更せず新しいデータを作る
    1. 商品一覧に「新商品」を追加する(元の一覧はそのまま)
    2. ユーザー設定を一部だけ変更した新しい設定を作る
  8. Day 23 前半 練習テンプレート(ブラウザで実行可能)
  9. Day 23 前半のまとめ
  10. Day 23:スプレッド構文(後半)
  11. 元のデータを変えないことの意味
    1. 直接書き換えると何が困るのか
    2. 「元のデータは触らず、新しいデータを作る」という発想
  12. 配列のコピーと結合の実践
    1. シナリオ:商品一覧の「更新履歴」を残したい
    2. シナリオ:検索結果に「おすすめ商品」を先頭に追加する
  13. オブジェクトのコピーと一部変更の実践
    1. シナリオ:ユーザーのステータスだけ変更したい
    2. シナリオ:設定オブジェクトの一部だけ変更したい
  14. 配列+オブジェクト+スプレッド構文の組み合わせ
    1. シナリオ:ToDo一覧に新しいタスクを追加する
    2. シナリオ:特定のタスクだけ状態を変えた新しい一覧を作る
  15. 関数とスプレッド構文
    1. シナリオ:ユーザー設定を更新する関数
  16. Day 23 後半 練習テンプレート(ブラウザで実行可能)
  17. Day 23 後半のまとめ

Day 23:スプレッド構文(前半)

「元のデータを壊さずに“新しいデータ”を作る力を身につける」

Day 23では、JavaScriptでとてもよく使われる スプレッド構文(... を学びます。

学習内容は次のとおりです。

  • ...(スプレッド構文)とは
  • 配列のコピー
  • 配列の結合
  • オブジェクトのコピー
  • 実践:データを変更せず新しいデータを作る

ここで身につけてほしいのは、

「元のデータを直接書き換えず、新しいデータを作る」

という考え方です。 これは、バグを減らし、安全なコードを書くうえで非常に重要な考え方です。

スプレッド構文(…)とは何か

「中身を“展開する”ための記号」

スプレッド構文は、... という3つのドットで書きます。

ざっくり言うと、

配列やオブジェクトの「中身」を、別の場所に“展開”するための記法

です。

まずはイメージから見てみます。

const numbers = [1, 2, 3];

// スプレッド構文で中身を展開する
const copy = [...numbers];

console.log(copy);    // [1, 2, 3]
console.log(numbers); // [1, 2, 3]
JavaScript
  • [...] の中で ...numbers と書くことで、 numbers の中身(1, 2, 3)がそのまま展開されます。
  • 結果として、copynumbers と同じ内容を持つ「別の配列」になります。

この「中身を展開する」という性質を使って、

  • コピー(複製)
  • 結合(くっつける)
  • 一部だけ変更した新しいデータを作る

といったことができるようになります。

配列のコピー:元の配列を壊さないための基本テクニック

なぜ「コピー」が必要なのか

まず、よくある危険なパターンを見てみます。

const original = [1, 2, 3];

// 直接代入すると「同じ配列」を指すことになります
const wrongCopy = original;

wrongCopy.push(4);

console.log('original:', original);   // [1, 2, 3, 4]
console.log('wrongCopy:', wrongCopy); // [1, 2, 3, 4]
JavaScript
  • wrongCopy = original; は「コピー」ではなく「同じものを指しているだけ」です。
  • wrongCopy を変更すると、original も一緒に変わってしまいます。

これは、バグの原因になりやすい危険な状態です。

スプレッド構文で「安全なコピー」を作る

const original = [1, 2, 3];

// スプレッド構文で中身を展開して、新しい配列を作ります
const copy = [...original];

copy.push(4); // copyだけに要素を追加

console.log('original:', original); // [1, 2, 3]
console.log('copy:', copy);         // [1, 2, 3, 4]
JavaScript
  • copyoriginal とは別の配列です。
  • copy を変更しても、original は変わりません。

重要ポイント:

「元の配列を壊したくないときは、まずスプレッド構文でコピーを作る」

という習慣をつけると、安全なコードに近づきます。

配列の結合:複数の配列を1つにまとめる

2つの配列をくっつける基本形

const a = [1, 2];
const b = [3, 4];

// スプレッド構文で中身を展開して、1つの配列にまとめます
const merged = [...a, ...b];

console.log(merged); // [1, 2, 3, 4]
JavaScript
  • ...a[1, 2] の中身を展開
  • ...b[3, 4] の中身を展開
  • それらを [] の中で並べることで、1つの配列になります。

途中に別の値を挟むこともできる

const a = [1, 2];
const b = [5, 6];

const merged = [...a, 3, 4, ...b];

console.log(merged); // [1, 2, 3, 4, 5, 6]
JavaScript
  • スプレッド構文で展開した中に、普通の値も混ぜられます。
  • 「既存の配列+新しい値+別の配列」という柔軟な結合ができます。

オブジェクトのコピー:元のオブジェクトを壊さない

危険なパターン:オブジェクトをそのまま代入する

const originalUser = {
  name: '山田太郎',
  age: 25,
};

// これは「コピー」ではなく「同じオブジェクト」を指しています
const wrongUserCopy = originalUser;

wrongUserCopy.age = 30;

console.log('originalUser:', originalUser);   // ageが30に変わっている
console.log('wrongUserCopy:', wrongUserCopy); // ageが30
JavaScript
  • wrongUserCopy を変更すると、originalUser も変わってしまいます。
  • 「元のデータを残しておきたい」というときには危険です。

スプレッド構文でオブジェクトをコピーする

const originalUser = {
  name: '山田太郎',
  age: 25,
};

// スプレッド構文で中身を展開して、新しいオブジェクトを作ります
const userCopy = { ...originalUser };

userCopy.age = 30;

console.log('originalUser:', originalUser); // { name: '山田太郎', age: 25 }
console.log('userCopy:', userCopy);         // { name: '山田太郎', age: 30 }
JavaScript
  • userCopyoriginalUser とは別のオブジェクトです。
  • userCopy を変更しても、originalUser は変わりません。

オブジェクトのコピー+一部変更:新しい状態を作る

「元のデータ+変更部分」で新しいオブジェクトを作る

const originalUser = {
  id: 101,
  name: '佐藤花子',
  age: 30,
  isPremium: false,
};

// プレミアム会員に昇格した新しいユーザー情報を作りたい
const updatedUser = {
  ...originalUser,   // 元のプロパティをすべてコピー
  isPremium: true,   // isPremiumだけ上書き
};

console.log('originalUser:', originalUser);
// { id: 101, name: '佐藤花子', age: 30, isPremium: false }

console.log('updatedUser:', updatedUser);
// { id: 101, name: '佐藤花子', age: 30, isPremium: true }
JavaScript
  • ...originalUser で元のプロパティをすべてコピーしています。
  • そのあとに isPremium: true と書くことで、 「元のデータをベースに、一部だけ変更した新しいオブジェクト」を作っています。

重要ポイント:

「元のデータはそのまま残しつつ、新しい状態を表すオブジェクトを作る」

という考え方は、 安全なデータ処理・バグの少ないコードにつながります。

実践:データを変更せず新しいデータを作る

ここからは、実務に近い形で「元のデータを壊さずに新しいデータを作る」例を見ていきます。

商品一覧に「新商品」を追加する(元の一覧はそのまま)

const products = [
  { id: 1, name: 'ノート', price: 100 },
  { id: 2, name: 'ペン',   price: 150 },
];

const newProduct = { id: 3, name: '消しゴム', price: 80 };

// 元のproductsを壊さずに、新しい一覧を作ります
const updatedProducts = [...products, newProduct];

console.log('元のproducts:', products);
// 変更されていない

console.log('新しいproducts一覧:', updatedProducts);
// 3件目として消しゴムが追加されている
JavaScript
  • updatedProducts は「新商品を含む新しい一覧」です。
  • products はそのまま残っています。

ユーザー設定を一部だけ変更した新しい設定を作る

const settings = {
  theme: 'dark',
  language: 'ja',
  notifications: true,
};

// 通知だけオフにした新しい設定を作りたい
const newSettings = {
  ...settings,
  notifications: false,
};

console.log('元のsettings:', settings);
// { theme: 'dark', language: 'ja', notifications: true }

console.log('新しいsettings:', newSettings);
// { theme: 'dark', language: 'ja', notifications: false }
JavaScript
  • 元の設定はそのまま残しつつ、 「通知だけオフにした新しい設定」を作っています。

Day 23 前半 練習テンプレート(ブラウザで実行可能)

以下のテンプレートをブラウザで開き、 コンソールで「配列のコピー・結合」「オブジェクトのコピー・一部変更」を確認してください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Day 23 前半:スプレッド構文 練習テンプレート</title>
</head>
<body>
  <h1>Day 23 前半:スプレッド構文(配列・オブジェクトのコピーと結合)</h1>
  <p>ブラウザのコンソールを開いて、ログを確認してください。</p>

  <script>
    console.log('--- 配列のコピー(危険なパターン) ---');

    const original = [1, 2, 3];
    const wrongCopy = original; // 同じ配列を参照しているだけ

    wrongCopy.push(4);

    console.log('original(危険なコピー後):', original);
    console.log('wrongCopy:', wrongCopy);

    console.log('--- 配列のコピー(スプレッド構文) ---');

    const safeOriginal = [1, 2, 3];
    const safeCopy = [...safeOriginal]; // 安全なコピー

    safeCopy.push(4);

    console.log('safeOriginal:', safeOriginal); // [1, 2, 3]
    console.log('safeCopy:', safeCopy);         // [1, 2, 3, 4]

    console.log('--- 配列の結合 ---');

    const a = [1, 2];
    const b = [3, 4];

    const merged = [...a, ...b];
    console.log('merged:', merged); // [1, 2, 3, 4]

    console.log('--- オブジェクトのコピー(危険なパターン) ---');

    const originalUser = { name: '山田太郎', age: 25 };
    const wrongUserCopy = originalUser;

    wrongUserCopy.age = 30;

    console.log('originalUser(危険なコピー後):', originalUser);
    console.log('wrongUserCopy:', wrongUserCopy);

    console.log('--- オブジェクトのコピー(スプレッド構文) ---');

    const safeUserOriginal = { name: '山田太郎', age: 25 };
    const safeUserCopy = { ...safeUserOriginal };

    safeUserCopy.age = 30;

    console.log('safeUserOriginal:', safeUserOriginal); // ageは25のまま
    console.log('safeUserCopy:', safeUserCopy);         // ageは30

    console.log('--- オブジェクトのコピー+一部変更 ---');

    const user = {
      id: 101,
      name: '佐藤花子',
      age: 30,
      isPremium: false,
    };

    const updatedUser = {
      ...user,
      isPremium: true,
    };

    console.log('元のuser:', user);
    console.log('更新後のupdatedUser:', updatedUser);

    console.log('--- 商品一覧に新商品を追加(元の一覧はそのまま) ---');

    const products = [
      { id: 1, name: 'ノート', price: 100 },
      { id: 2, name: 'ペン',   price: 150 },
    ];

    const newProduct = { id: 3, name: '消しゴム', price: 80 };

    const updatedProducts = [...products, newProduct];

    console.log('元のproducts:', products);
    console.log('新しいproducts一覧:', updatedProducts);

    console.log('--- 設定オブジェクトの一部だけ変更 ---');

    const settings = {
      theme: 'dark',
      language: 'ja',
      notifications: true,
    };

    const newSettings = {
      ...settings,
      notifications: false,
    };

    console.log('元のsettings:', settings);
    console.log('新しいsettings:', newSettings);

    console.log('--- Day 23 前半:スプレッド構文の練習が完了しました ---');
  </script>
</body>
</html>

Day 23 前半のまとめ

Day 23前半では、

  • スプレッド構文(...)が「中身を展開する」ための記法であること
  • 配列のコピーを安全に行う方法
  • 配列同士を結合して新しい配列を作る方法
  • オブジェクトのコピーを安全に行う方法
  • 元のデータを壊さずに「一部だけ変更した新しいデータ」を作る方法

を確認しました。

ここでのキーワードは、

「元のデータを直接いじらず、新しいデータを作る」

という考え方です。 この考え方は、今後のJavaScript学習だけでなく、 大規模なアプリケーションやセキュアなコードを書くうえでも、とても重要な土台になります。


Day 23:スプレッド構文(後半)

「“元のデータを守りながら”新しいデータを作る実践編」

Day 23後半では、前半で学んだスプレッド構文(...)を、 もう一歩踏み込んで「実務に近い形」で使っていきます。

テーマは引き続き、

  • 配列のコピー
  • 配列の結合
  • オブジェクトのコピー
  • 実践:データを変更せず新しいデータを作る

ですが、ここでは 「なぜ元のデータを変えないことが大事なのか」 を意識しながら、 具体的なシナリオを通して理解を深めていきます。

元のデータを変えないことの意味

「“安全なコード”と“バグの少なさ”につながる考え方」

直接書き換えると何が困るのか

例えば、商品一覧を管理しているコードがあるとします。

const products = [
  { id: 1, name: 'ノート', price: 100 },
  { id: 2, name: 'ペン',   price: 150 },
];
JavaScript

この products を、画面表示用に少し加工したいとき、 もし「直接書き換え」をしてしまうと、後から別の処理が products を使ったときに、 「いつの間にか中身が変わっている」という状態になりかねません。

// 直接書き換えてしまう危険な例
products.push({ id: 3, name: '消しゴム', price: 80 });

// ここから先のコードは、「元のproductsが2件だけ」という前提で書かれているかもしれません
JavaScript

こうした「どこかで勝手に変わっている」状態は、 バグの原因になりやすく、セキュリティ的にも好ましくありません。

「元のデータは触らず、新しいデータを作る」という発想

そこで重要になるのが、

元のデータはそのまま残しておき、 必要に応じて「新しいデータ」を作る

という発想です。

スプレッド構文は、この発想をコードで実現するための強力な道具です。

配列のコピーと結合の実践

「履歴を残しながら、新しい状態を作る」

シナリオ:商品一覧の「更新履歴」を残したい

商品一覧を更新していくときに、 「前の状態も残しておきたい」ということがあります。

const productsV1 = [
  { id: 1, name: 'ノート', price: 100 },
  { id: 2, name: 'ペン',   price: 150 },
];

const newProduct = { id: 3, name: '消しゴム', price: 80 };

// 新しいバージョン(productsV2)を作ります
const productsV2 = [...productsV1, newProduct];

console.log('初期バージョン productsV1:', productsV1);
console.log('更新後バージョン productsV2:', productsV2);
JavaScript
  • productsV1 は「初期状態」として残しておきます。
  • productsV2 は「新商品を追加した後の状態」です。
  • どちらも配列ですが、スプレッド構文のおかげで「別々の状態」として扱えます。

シナリオ:検索結果に「おすすめ商品」を先頭に追加する

検索結果の配列があり、 その先頭に「おすすめ商品」を追加したいとします。

const searchResults = [
  { id: 2, name: 'ペン',   price: 150 },
  { id: 3, name: '消しゴム', price: 80 },
];

const recommended = { id: 1, name: 'ノート', price: 100 };

// 元のsearchResultsはそのままにして、表示用の配列を作ります
const displayResults = [recommended, ...searchResults];

console.log('元の検索結果:', searchResults);
console.log('表示用の検索結果:', displayResults);
JavaScript
  • displayResults は「おすすめ商品を先頭にした新しい配列」です。
  • searchResults は「元の検索結果」としてそのまま残っています。

オブジェクトのコピーと一部変更の実践

「ユーザー情報・設定情報を安全に更新する」

シナリオ:ユーザーのステータスだけ変更したい

ユーザー情報を表すオブジェクトがあるとします。

const user = {
  id: 101,
  name: '佐藤花子',
  email: 'hanako@example.com',
  isPremium: false,
};
JavaScript

このユーザーがプレミアム会員になったとき、 「元の情報は残しつつ、新しい状態を作る」ことを考えます。

const premiumUser = {
  ...user,        // 元のプロパティをすべてコピー
  isPremium: true // isPremiumだけ上書き
};

console.log('元のuser:', user);
console.log('プレミアム版 premiumUser:', premiumUser);
JavaScript
  • user は「プレミアムになる前の状態」として残ります。
  • premiumUser は「プレミアムになった後の状態」です。

シナリオ:設定オブジェクトの一部だけ変更したい

アプリの設定を表すオブジェクトがあるとします。

const settings = {
  theme: 'dark',
  language: 'ja',
  notifications: true,
};
JavaScript

ここで、「通知だけオフにした設定」を作りたい場合、 元の settings を直接書き換えるのではなく、 新しい設定オブジェクトを作ります。

const silentSettings = {
  ...settings,
  notifications: false,
};

console.log('元のsettings:', settings);
console.log('通知オフのsilentSettings:', silentSettings);
JavaScript
  • settings は「通常の設定」として残ります。
  • silentSettings は「通知オフの設定」として別に存在します。

配列+オブジェクト+スプレッド構文の組み合わせ

「一覧データを安全に更新する」

シナリオ:ToDo一覧に新しいタスクを追加する

const todos = [
  { id: 1, title: 'JavaScriptの勉強', done: false },
  { id: 2, title: '買い物に行く',     done: true  },
];

const newTodo = { id: 3, title: 'メールの返信', done: false };

// 元のtodosはそのままにして、新しい一覧を作ります
const updatedTodos = [...todos, newTodo];

console.log('元のtodos:', todos);
console.log('更新後のupdatedTodos:', updatedTodos);
JavaScript
  • todos は「更新前の一覧」として残ります。
  • updatedTodos は「新しいタスクを含む一覧」です。

シナリオ:特定のタスクだけ状態を変えた新しい一覧を作る

「idが2のタスクを完了にした新しい一覧」を作りたいとします。

const todos = [
  { id: 1, title: 'JavaScriptの勉強', done: false },
  { id: 2, title: '買い物に行く',     done: false },
];

const updatedTodos = todos.map((todo) => {
  if (todo.id === 2) {
    // idが2のタスクだけ、doneをtrueにした新しいオブジェクトを返します
    return {
      ...todo,
      done: true,
    };
  }

  // それ以外は元のままコピーして返します
  return { ...todo };
});

console.log('元のtodos:', todos);
console.log('更新後のupdatedTodos:', updatedTodos);
JavaScript
  • map() とスプレッド構文を組み合わせることで、 「一覧の中の一部だけ変更した新しい一覧」を作れます。
  • 元の todos は一切変更されていません。

関数とスプレッド構文

「柔軟な引数・安全な更新ロジックを作る」

シナリオ:ユーザー設定を更新する関数

const defaultSettings = {
  theme: 'light',
  language: 'ja',
  notifications: true,
};

// 部分的な更新内容を受け取り、新しい設定を返す関数
function updateSettings(currentSettings, partialUpdate) {
  return {
    ...currentSettings, // 現在の設定をコピー
    ...partialUpdate,   // 更新したい部分を上書き
  };
}

const userSettings = { ...defaultSettings };

const newUserSettings = updateSettings(userSettings, {
  theme: 'dark',
  notifications: false,
});

console.log('元のuserSettings:', userSettings);
console.log('更新後のnewUserSettings:', newUserSettings);
JavaScript
  • updateSettings は「元の設定+更新内容」から新しい設定を作る関数です。
  • スプレッド構文を使うことで、 「どの部分を上書きするか」が明確になります。

Day 23 後半 練習テンプレート(ブラウザで実行可能)

以下のテンプレートをブラウザで開き、 コンソールで「元のデータを変更せず新しいデータを作る」流れを確認してください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Day 23 後半:スプレッド構文 実践テンプレート</title>
</head>
<body>
  <h1>Day 23 後半:スプレッド構文(データを変更せず新しいデータを作る)</h1>
  <p>ブラウザのコンソールを開いて、ログを確認してください。</p>

  <script>
    console.log('--- 商品一覧のバージョン管理 ---');

    const productsV1 = [
      { id: 1, name: 'ノート', price: 100 },
      { id: 2, name: 'ペン',   price: 150 },
    ];

    const newProduct = { id: 3, name: '消しゴム', price: 80 };

    const productsV2 = [...productsV1, newProduct];

    console.log('productsV1:', productsV1);
    console.log('productsV2:', productsV2);

    console.log('--- 検索結果におすすめ商品を追加 ---');

    const searchResults = [
      { id: 2, name: 'ペン',   price: 150 },
      { id: 3, name: '消しゴム', price: 80 },
    ];

    const recommended = { id: 1, name: 'ノート', price: 100 };

    const displayResults = [recommended, ...searchResults];

    console.log('元のsearchResults:', searchResults);
    console.log('表示用displayResults:', displayResults);

    console.log('--- ユーザーのステータス更新(元のデータは保持) ---');

    const user = {
      id: 101,
      name: '佐藤花子',
      email: 'hanako@example.com',
      isPremium: false,
    };

    const premiumUser = {
      ...user,
      isPremium: true,
    };

    console.log('元のuser:', user);
    console.log('premiumUser:', premiumUser);

    console.log('--- 設定オブジェクトの一部だけ変更 ---');

    const settings = {
      theme: 'dark',
      language: 'ja',
      notifications: true,
    };

    const silentSettings = {
      ...settings,
      notifications: false,
    };

    console.log('元のsettings:', settings);
    console.log('silentSettings:', silentSettings);

    console.log('--- ToDo一覧に新しいタスクを追加 ---');

    const todos = [
      { id: 1, title: 'JavaScriptの勉強', done: false },
      { id: 2, title: '買い物に行く',     done: true  },
    ];

    const newTodo = { id: 3, title: 'メールの返信', done: false };

    const updatedTodos = [...todos, newTodo];

    console.log('元のtodos:', todos);
    console.log('updatedTodos:', updatedTodos);

    console.log('--- 特定のToDoだけ状態を変更した新しい一覧を作る ---');

    const todosForUpdate = [
      { id: 1, title: 'JavaScriptの勉強', done: false },
      { id: 2, title: '買い物に行く',     done: false },
    ];

    const updatedTodos2 = todosForUpdate.map((todo) => {
      if (todo.id === 2) {
        return {
          ...todo,
          done: true,
        };
      }
      return { ...todo };
    });

    console.log('元のtodosForUpdate:', todosForUpdate);
    console.log('updatedTodos2:', updatedTodos2);

    console.log('--- 設定更新関数の実践 ---');

    const defaultSettings = {
      theme: 'light',
      language: 'ja',
      notifications: true,
    };

    function updateSettings(currentSettings, partialUpdate) {
      return {
        ...currentSettings,
        ...partialUpdate,
      };
    }

    const userSettings = { ...defaultSettings };

    const newUserSettings = updateSettings(userSettings, {
      theme: 'dark',
      notifications: false,
    });

    console.log('元のuserSettings:', userSettings);
    console.log('newUserSettings:', newUserSettings);

    console.log('--- Day 23 後半:スプレッド構文の実践が完了しました ---');
  </script>
</body>
</html>

Day 23 後半のまとめ

Day 23後半では、

  • 「元のデータを直接書き換えず、新しいデータを作る」ことの意味
  • スプレッド構文で配列・オブジェクトをコピーし、安全に更新する方法
  • 一覧データ(商品一覧・ToDo一覧など)を、 スプレッド構文と組み合わせて安全に更新する方法
  • 関数の中でスプレッド構文を使い、「部分的な更新」をわかりやすく書く方法

を確認しました。

スプレッド構文は、 「データの安全性」と「コードの読みやすさ」を両立させるための武器」 だと意識して使っていくと、 実務レベルのコードに近づいていく感覚をつかめるはずです。

タイトルとURLをコピーしました