3秒でわかる
値そのものではなく、データが置かれた場所への参照を変数に持つ型。代入すると中身を共有するため、片方を書き換えると両方が変わります。
もう少し詳しく
どういうものか
参照型は、変数の中にデータ本体ではなく、データが置かれている場所を示す情報を持つ型です。配列、オブジェクト、クラスのインスタンス、関数などが該当します。対になるのが値型(プリミティブ)で、数値や真偽値のように変数へ中身が直接入るものを指します。
イメージにすると次のようになります。
値型 x = 5 [ x | 5 ]
参照型 a = [1,2,3] [ a | 0x8f2 ] ---> [1, 2, 3]
b = a [ b | 0x8f2 ] ---/ 同じ配列を指すなぜ必要か
大きなデータを扱うたびに丸ごと複製していては、メモリも時間も足りません。1万件の配列を関数へ渡すたびに全要素をコピーする代わりに、置き場所を示す小さな情報だけを渡すのが参照型の考え方です。副作用として、渡した先で中身を書き換えると呼び出し元にも反映されます。これは便利さと事故の両方の源になります。
具体例
// 値型は独立している
let x = 5;
let y = x;
y = 10;
console.log(x); // 5 のまま
// 参照型は同じ実体を共有する
const a = { count: 1 };
const b = a;
b.count = 99;
console.log(a.count); // 99 に変わっている
// 比較も中身ではなく参照で行われる
console.log({ v: 1 } === { v: 1 }); // falseコピーを作りたいときは、明示的に新しい実体を用意します。
const copied = { ...a }; // 1段だけの複製
const deep = structuredClone(a); // 入れ子まで含めた複製つまずきやすいところ
スプレッド構文による複製は1段目までしか効きません。{ ...user } で複製しても、user.address が入れ子のオブジェクトなら、複製後も同じ住所オブジェクトを共有しています。片方を書き換えると両方が変わります。
関数の引数も同じです。引数として渡したオブジェクトを関数内で書き換えると、呼び出し元の値が変わります。渡された側で書き換えない、書き換えるなら複製してから、という方針を決めておくと事故が減ります。
Javaでは String の比較で == を使う誤りが定番です。== は参照が同じかを見るので、内容が同じでも別インスタンスなら偽になります。内容を比べるときは equals を使います。
似た用語との違い
参照型とポインタは近い概念ですが、ポインタはアドレスそのものを数値として扱えて演算もできるのに対し、JavaやJavaScriptの参照は差し替えと参照先へのアクセスしかできません。危険な操作を言語側で塞いだポインタ、と捉えると位置づけが分かります。