目次
概要
JavaScriptのオブジェクトは「値渡し」で扱われるため、単に const b = a; と代入しただけではコピーにならず、b を変更すると a も変わってしまいます。
別の独立したデータとして扱いたい場合は、明示的に「複製(クローン)」を作成する必要があります。現代のJavaScriptでは、スプレッド構文 {...obj} を使うのが主流です。
今回は、RPGのキャラクター作成をテーマに、テンプレート(ひな形)から新しいキャラクター設定を複製・カスタマイズする処理を実装します。
仕様(構文)
オブジェクトのコピー方法
| 手法 | 構文 | 特徴 |
| スプレッド構文 | const copy = { ...original }; | 推奨。ES2018で登場。記述が簡潔で読みやすい。 |
| Object.assign | const copy = Object.assign({}, original); | 従来の方法。複数のオブジェクトをマージする場合にも使われる。 |
- 注意点(重要): どちらも シャローコピー(浅いコピー) です。
- オブジェクトの直下のプロパティは複製されますが、ネスト(入れ子)されている配列やオブジェクトは「参照」のまま共有されます。
基本の使い方
1. スプレッド構文 {...}
オブジェクトを展開して、新しい波括弧 {} の中に入れます。
const baseStats = { hp: 100, mp: 50 };
// 複製と同時にプロパティの上書き・追加も可能
const heroStats = { ...baseStats, mp: 200, attack: 15 };
console.log(heroStats);
// { hp: 100, mp: 200, attack: 15 }
2. Object.assign()
空のオブジェクト {} に対して、元のオブジェクトの中身をマージします。
const baseStats = { hp: 100, mp: 50 };
const enemyStats = Object.assign({}, baseStats);
コード全文(HTML / JavaScript)
ゲームのキャラクターメイク画面です。
「基本の戦士セット(テンプレート)」を複製し、新しいキャラクター「勇者」と「魔法剣士」を作成します。また、シャローコピーの注意点(装備品の配列操作)についても確認します。
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Character Maker</title>
<style>
.maker-panel {
font-family: 'Courier New', monospace;
max-width: 600px;
background-color: #2c3e50;
color: #ecf0f1;
padding: 20px;
border-radius: 8px;
}
h3 { margin-top: 0; color: #f1c40f; border-bottom: 2px solid #7f8c8d; padding-bottom: 10px; }
.char-box {
background-color: #34495e;
margin-bottom: 15px;
padding: 15px;
border-radius: 4px;
border-left: 5px solid #bdc3c7;
}
.char-title {
font-weight: bold;
color: #e67e22;
margin-bottom: 5px;
display: block;
}
.stats {
font-size: 0.9rem;
line-height: 1.6;
}
.equipment {
color: #1abc9c;
}
button {
background-color: #e74c3c;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
margin-top: 10px;
}
button:hover { background-color: #c0392b; }
.note {
font-size: 0.8rem;
color: #95a5a6;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="maker-panel">
<h3>Character Setting Clone</h3>
<div id="char-list">
</div>
<button id="btn-clone">キャラクターを複製して作成</button>
<div class="note">※コンソールでシャローコピーの挙動も確認してください</div>
</div>
<script src="char_clone.js"></script>
</body>
</html>
JavaScript
/**
* キャラクター作成スクリプト
* オブジェクトの複製とシャローコピーの検証
*/
// 1. テンプレートとなるオブジェクト(コピー元)
const templateChar = {
job: 'Warrior',
level: 1,
stats: {
str: 10,
int: 5
},
// 配列(参照型)を含む
equipment: ['Copper Sword', 'Leather Armor']
};
const listArea = document.getElementById('char-list');
const btnClone = document.getElementById('btn-clone');
/**
* キャラ情報をHTMLとして表示する関数
*/
const renderChar = (name, charObj) => {
const div = document.createElement('div');
div.className = 'char-box';
div.innerHTML = `
<span class="char-title">[${name}]</span>
<div class="stats">
Job: ${charObj.job} / Lv: ${charObj.level}<br>
Stats: STR=${charObj.stats.str}, INT=${charObj.stats.int}<br>
Equip: <span class="equipment">${charObj.equipment.join(', ')}</span>
</div>
`;
listArea.appendChild(div);
};
/**
* 複製処理の実行
*/
const createCharacters = () => {
listArea.innerHTML = ''; // クリア
// 1. スプレッド構文 {...} で複製(推奨)
// 勇者を作成: Jobを書き換え、Lvを変更
const hero = {
...templateChar,
job: 'Hero',
level: 50
};
// 2. Object.assign({}, ...) で複製
// 魔法剣士を作成
const magicKnight = Object.assign({}, templateChar);
magicKnight.job = 'Magic Knight';
magicKnight.stats = { str: 15, int: 15 }; // statsオブジェクトごと上書き(安全)
// ★ シャローコピーの注意点の実証
// heroの装備を変更してみる
// pushなどの破壊的メソッドを使うと、元のtemplateCharの配列も変わってしまう(参照が同じだから)
hero.equipment.push('Hero Shield');
// 表示
renderChar('Template (Original)', templateChar);
renderChar('Hero (Spread Clone)', hero);
renderChar('Magic Knight (Assign Clone)', magicKnight);
console.log('--- Shallow Copy Check ---');
console.log('Template Equip:', templateChar.equipment); // 盾が増えてしまっている!
console.log('Hero Equip:', hero.equipment);
};
// イベントリスナー
btnClone.addEventListener('click', createCharacters);
カスタムポイント
- ディープコピー(深いコピー):ネストされた配列やオブジェクトも含めて完全に独立したコピーを作りたい場合は、
structuredClone()を使用します。JavaScriptconst deepCopy = structuredClone(originalObject);これを使えば、equipment配列を変更してもコピー元には影響しません。
注意点
- 構文の違い:
- 配列のコピー:
[...array] - オブジェクトのコピー:
{...object}括弧の種類を間違えないようにしましょう。
- 配列のコピー:
- シャローコピーのリスク:上記のコード例にある通り、コピーしたオブジェクト内の「配列」や「子オブジェクト」を変更(
push等)すると、コピー元まで書き換わってしまいます。これを防ぐには、ネストされた部分も個別にコピーするか、structuredCloneを使う必要があります。
まとめ
- 基本:
{ ...obj }でコピーを作成し、元のデータを保持したまま新しいデータを作る。 - 注意: ネストされたデータは「参照」がコピーされるだけ(シャローコピー)なので、変更時の副作用に注意する。
- 解決策: 完全に切り離したい場合は
structuredClone(obj)を使う。
