JavaScriptの「値渡し」と「参照渡し」をやさしく理解する
はじめて出会うと戸惑うポイントだけど、ここを掴むと「なんでバグったの?」がグッと減ります。ゆっくり、具体例と練習問題で体に落とし込みましょう。
基本の考え方
- 箱のイメージ: 変数は「箱」。プリミティブ型は「中身そのもの」をコピー、オブジェクト型は「同じ箱の場所(地図)=参照」をコピーします。
- プリミティブ型: 数値、文字列、真偽値、
null、undefined、Symbol、BigInt。コピーは独立。 - オブジェクト型: 配列、オブジェクト、関数、日付、正規表現など。コピーは同じものの「共有」になります。
プリミティブ型は「値そのものをコピー」
- ポイント: 値を渡すたびに新しい独立した値になるので、片方を変えてももう片方は変わりません。
let a = 10;
let b = a; // 10 をコピー
b = 20;
console.log(a); // 10 (aは変わらない)
console.log(b); // 20
JavaScript- よくある場面:
- 数値計算: 足し算や引き算で元の値が勝手に変わらない。
- 文字列操作: 文字列は不変(イミュータブル)なので、代入先とは独立。
オブジェクト型は「同じものを共有(参照渡し)」
- ポイント: 代入すると「参照(場所)」がコピーされ、同じ実体を一緒に触ることになります。
let a = [10, 20];
let b = a; // 同じ配列への参照をコピー
b[0] = 99;
console.log(a); // [99, 20] (aも変わる)
console.log(b); // [99, 20]
JavaScript- オブジェクトでも同じ:
let user1 = { name: "Miki", age: 20 };
let user2 = user1; // 同じオブジェクトを参照
user2.age = 21;
console.log(user1.age); // 21(user1も変わる)
JavaScript- 落とし穴: 「別の変数に入れたから安全」と思って変更すると、元のデータも変わっていてバグの原因になります。
##「ちゃんとコピーしたい」ときの方法
配列の浅いコピー(一次元・中身がプリミティブ中心ならOK)
- スプレッド構文:
const a = [1, 2, 3];
const b = [...a]; // 新しい配列を作る
b[0] = 9;
console.log(a); // [1, 2, 3]
console.log(b); // [9, 2, 3]
JavaScript- slice:
const a = [1, 2, 3];
const b = a.slice();
JavaScriptオブジェクトの浅いコピー
- スプレッド構文:
const user1 = { name: "Ryo", score: 80 };
const user2 = { ...user1 };
user2.score = 90;
console.log(user1.score); // 80
console.log(user2.score); // 90
JavaScript- Object.assign:
const user2 = Object.assign({}, user1);
JavaScript浅いコピーの注意(入れ子の中身は共有される)
- ポイント: ネストした配列・オブジェクトの「中の中」は同じ参照を持ち続けます。
const a = [{ x: 1 }, { x: 2 }];
const b = [...a]; // 浅いコピー
b[0].x = 999;
console.log(a[0].x); // 999(中身のオブジェクトは共有されてる)
JavaScript深いコピー(入れ子も含めて完全に別物)
- 手軽な方法(JSON): 単純なデータなら便利。ただし関数・
Date・undefinedは消えるため注意。
const a = { p: 1, q: { r: 2 } };
const b = JSON.parse(JSON.stringify(a));
b.q.r = 999;
console.log(a.q.r); // 2
JavaScript- より安全な方法:
structuredClone(a)(対応環境なら推奨)。関数はコピーされないが、DateやMapなど多くの型に対応。
const a = { when: new Date(), list: [{ v: 1 }] };
const b = structuredClone(a);
JavaScriptよくある失敗と回避パターン
- 同じデータを見ているときに部分更新する
- 失敗例: リストを別の変数に入れてから編集したら、元の画面のリストも勝手に変わる。
- 回避: 配列・オブジェクトは必要な箇所だけ新しいコピーを作る。
- 浅いコピーで安心してしまう
- 失敗例: スプレッドでコピーしたのに、中の配列やオブジェクトが連動して変わる。
- 回避: ネストがあるなら深いコピー。あるいは更新箇所だけイミュータブルに作り直す。
- 関数・日付・
undefinedがJSONで消える- 回避:
structuredCloneを検討。ダメなら手動で各フィールドを新規生成。
- 回避:
練習問題
例題1:プリミティブ型の独立性
- 問題: 次の出力はどうなる?
let x = "Hello";
let y = x;
y = "World";
console.log(x);
console.log(y);
JavaScript- 答えの考え方: 文字列はプリミティブ。コピーは独立。
- 期待出力:
Hello
World
例題2:参照の共有(配列)
- 問題: 次の出力はどうなる?
let a = [1, 2];
let b = a;
a.push(3);
b[0] = 9;
console.log(a);
console.log(b);
JavaScript- 答えの考え方:
aとbは同じ配列を共有。 - 期待出力:
[9, 2, 3]
[9, 2, 3]
例題3:浅いコピーとネスト
- 問題: 出力は?
const original = [{ n: 1 }, { n: 2 }];
const copy = [...original];
copy[1].n = 100;
console.log(original[1].n);
JavaScript- 答えの考え方: 浅いコピーなので内側のオブジェクトは共有。
- 期待出力:
100
例題4:安全にコピーしてから編集
- 問題: 配列
[ {x:1}, {x:2} ]の完全コピーを作って、コピー側の最初の要素だけx=999にしたい。元は変えないで。 - 解答例(structuredClone推奨):
const arr = [{ x: 1 }, { x: 2 }];
const safe = structuredClone(arr);
safe[0].x = 999;
console.log(arr[0].x); // 1 (元は変わらない)
console.log(safe[0].x); // 999
JavaScript- 解答例(JSONで代用・単純データ限定):
const arr = [{ x: 1 }, { x: 2 }];
const safe = JSON.parse(JSON.stringify(arr));
まとめと次の一歩
- プリミティブは独立、オブジェクトは共有。
- 浅いコピーは「形」だけ別、深いコピーは「中身」まで別。
- ネストがあるデータは深いコピーか、更新箇所だけ作り直す。

