JavaScript | 「値渡し」と「参照渡し」

Java
スポンサーリンク

JavaScriptの値渡しと参照渡しを、初心者向けにやさしく解説

最初はややこしく感じるところですが、「何がコピーされて、何が共有されるのか」をつかむと急にわかりやすくなります。例題を動かしながら、直感で理解していきましょう。


基本の考え方

  • 値渡し: 数字や文字など「小さくて単純な値」は、その場でコピーされる。変数ごとに独立。
  • 参照渡し: 配列やオブジェクトなど「箱に詰めた複数の値」は、箱そのものではなく「箱への案内状(参照)」がコピーされる。案内状が同じなら、中身を変えると両方に影響。

値渡し(プリミティブ型)

  • 対象: 数値、文字列、真偽値、nullundefinedsymbolbigint
  • 直感: 「メモに書いた数字をもう一枚の紙に写す」イメージ。紙は別々なので、片方を書き換えてももう片方は変わらない。

例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(応用): ネストしたオブジェクトを深いコピーしたいとき、どんな方法があるか調べて、短い例を書いてみてください。

さいごに押さえる核心

  • プリミティブは「値」がコピーされる。
  • オブジェクトは「参照(合鍵)」がコピーされる。
  • コピーのつもりが共有になっていないか、常に意識する。
タイトルとURLをコピーしました