JavaScriptの値渡しと参照渡しを、初心者向けにやさしく解説
最初はややこしく感じるところですが、「何がコピーされて、何が共有されるのか」をつかむと急にわかりやすくなります。例題を動かしながら、直感で理解していきましょう。
基本の考え方
- 値渡し: 数字や文字など「小さくて単純な値」は、その場でコピーされる。変数ごとに独立。
- 参照渡し: 配列やオブジェクトなど「箱に詰めた複数の値」は、箱そのものではなく「箱への案内状(参照)」がコピーされる。案内状が同じなら、中身を変えると両方に影響。
値渡し(プリミティブ型)
- 対象: 数値、文字列、真偽値、
null、undefined、symbol、bigint - 直感: 「メモに書いた数字をもう一枚の紙に写す」イメージ。紙は別々なので、片方を書き換えてももう片方は変わらない。
例1:数字は独立している
let a = 10;
let b = a; // 10をコピー
b = 99; // bだけを書き換え
console.log(a); // 10(aは変わらない)
console.log(b); // 99
JavaScript例2:文字列も独立している
let msg1 = "hello";
let msg2 = msg1; // "hello"をコピー
msg2 = "bye"; // msg2だけ変更
console.log(msg1); // "hello"
console.log(msg2); // "bye"
JavaScript参照渡し(オブジェクト型)
- 対象: 配列、オブジェクト、関数、日付、正規表現など
- 直感: 「同じ倉庫への合鍵を複製しただけ」イメージ。鍵が同じなら、倉庫の中身を変えると、どの鍵で見ても中身は変わっている。
例3:配列は中身が共有される
let listA = [1, 2, 3];
let listB = listA; // 同じ倉庫(配列)への鍵を複製
listB[0] = 99; // 倉庫の中身を書き換え
console.log(listA); // [99, 2, 3](listAから見ても変わっている)
console.log(listB); // [99, 2, 3]
JavaScript例4:オブジェクトも同じ
let userA = { name: "Mika", age: 20 };
let userB = userA; // 同じオブジェクトへの鍵
userB.age = 21; // 中身を書き換え
console.log(userA.age); // 21(userAも変わる)
console.log(userB.age); // 21
JavaScript「コピーしたつもり問題」を避ける方法
- 配列・オブジェクトを本当にコピーしたい: 新しい箱を作り、中身を移す(浅いコピー)。中身がさらに箱なら「深いコピー」が必要になることもある。
例5:配列の浅いコピー
const a = [1, 2, 3];
const b = [...a]; // スプレッド構文で新しい配列を作る
b[0] = 100;
console.log(a); // [1, 2, 3](変わらない)
console.log(b); // [100, 2, 3]
JavaScript例6:オブジェクトの浅いコピー
const u1 = { name: "Aki", score: 50 };
const u2 = { ...u1 }; // 新しいオブジェクトを作る
u2.score = 80;
console.log(u1.score); // 50(変わらない)
console.log(u2.score); // 80
JavaScript例7:浅いコピーの注意(ネストがある場合)
const original = { info: { city: "Tokyo" } };
const copy = { ...original }; // 浅いコピー(内側のオブジェクトは共有)
copy.info.city = "Osaka";
console.log(original.info.city); // "Osaka"(内側は共有されていて変わる)
JavaScript- 深いコピーが必要なとき: ネストしたオブジェクトや配列の中身まで新規作成する。学習初期はまず「浅いコピーは内側が共有される」と覚えておけば十分です。
関数に渡すときの挙動
- プリミティブは値渡し: 関数内で変更しても元の変数は変わらない。
- オブジェクトは参照渡し(参照の値が渡される): 関数内で中身をいじると元の変数の見える世界が変わる。
例8:プリミティブを関数に渡す
function addOne(x) {
x = x + 1; // 関数内のxだけが変わる
}
let n = 5;
addOne(n);
console.log(n); // 5(変わらない)
JavaScript例9:オブジェクトを関数に渡す
function levelUp(player) {
player.level += 1; // 同じ倉庫の中身を変更
}
let p = { name: "Ken", level: 1 };
levelUp(p);
console.log(p.level); // 2(元のオブジェクトが変わる)
JavaScriptハマりがちな落とし穴と対策
- 同じ参照を持っているのに気づかない: 代入でコピーしただけだと「合鍵」になる。変更が意図せず伝播する。
- イミュータブルに扱いたいとき: 新しい配列・オブジェクトを作ってから変更する。
- 配列:
const newArr = oldArr.map(x => x)や[...oldArr] - オブジェクト:
const newObj = { ...oldObj, changedKey: newValue }
- 配列:
- 比較の誤解: オブジェクト同士の比較
===は「中身が同じか」ではなく「同じ倉庫か(同じ参照か)」を見る。
const a = { x: 1 };
const b = { x: 1 };
console.log(a === b); // false(倉庫が別)
const c = a;
console.log(a === c); // true(同じ倉庫)
JavaScript手を動かす練習問題
- 問題1: 次のコードの出力を予想してから実行してください。理由も説明してみましょう。
let x = 3;
let y = x;
y += 5;
console.log(x, y); // ?
JavaScript- 問題2: 次のコードで
userA.nameは何になりますか?
const userA = { name: "Rin" };
const userB = userA;
userB.name = "Kai";
console.log(userA.name); // ?
JavaScript- 問題3: 次の
settingsを安全にコピーしてthemeだけ”dark”に変えてください(浅いコピーでOK)。
const settings = { theme: "light", lang: "ja" };
// ここにコピーして変更するコードを書く
JavaScript- 問題4(応用): ネストしたオブジェクトを深いコピーしたいとき、どんな方法があるか調べて、短い例を書いてみてください。
さいごに押さえる核心
- プリミティブは「値」がコピーされる。
- オブジェクトは「参照(合鍵)」がコピーされる。
- コピーのつもりが共有になっていないか、常に意識する。
