JavaScriptにおける参照

2022.10.20
はじめに
JavaScriptには明示的なポインタはありませんが、コピーした変数が同じ先を参照しているのか、変更すると他方に反映されるのかを意識する場面が多々あります。
そこで、できれば網羅的、実践的なクイズを用意してみました。
1問目
例題みたいなもんです
const func = (num, obj) => {
num = 1
obj.x = 'changed'
}
const foo = 0
const bar = {
x: 'unchanged'
}
func(foo, bar)
console.log(foo, bar)
どんな出力になるか考えてみてください
解答
0 {x: 'changed'}
一応解説
文字列や数値や真偽値のようなprimitiveな値はそのまま、配列やオブジェクトなどは参照として値を持つと考えると良いと思います。func
は数値とオブジェクトを引数にとり、関数内で扱う変数は実際に引数として渡された変数のコピーと考えます。数値はそれ自体がコピーされるので関数内で値が変更されても関数の外では変わりません。
一方オブジェクトは参照を持つため、コピーされても参照が指す先は変わらず、変更が関数外にも反映されると考えられます。
2問目
この記事を書くきっかけになったコード
const objArr = [{p: 0}, {p: 1}, {p: 2}];
const item = objArr.find((obj) => obj.p === 1);
item.p = 3;
console.log(objArr);
どんな出力になるか考えてみてください
解答
[{p: 0}, {p: 3}, {p: 2}]
解説
itemはオブジェクトなので配列内の2番目の要素と同じ参照を持ち、変更が反映されると考えられます。ですが可読性が落ちるため、配列内のオブジェクトを変更する際はfindIndex
で直接変更することをおすすめします。
3問目
const arr1 = [{p: 0}, {p: 1}, {p: 2}]
const arr2 = [...arr1]
arr1[0].p = 1
console.log(arr2)
どんな出力になるか考えてみてください
解答
[{p: 1}, {p: 1}, {p: 2}]
解説
スプレッド演算子はコピーを作りますが、コピーされても参照は同じ場所を指します。よってarr2
にも変更が反映されます。
4問目
const obj1 = {
x: 0,
y: 0,
z: {
a: 0,
b: 0
}
}
const obj2 = {...obj1}
obj1.x = 1
obj1.z.a = 1
console.log(obj2)
どんな出力になるか考えてみてください
解答
{
x: 0,
y: 0,
z: {
a: 1,
b: 0
}
}
解説
スプレッド演算子をオブジェクトに使う、primitiveな値をvalueにもつxは値自体がコピーされ、参照であるオブジェクトをもつzは参照自体がコピーされます。
よってobj2.z.a
のみが変更されるようです。
最後に
これまであまり意識してこなかったのですが今回記事を書いてみて、これまで複雑だと思っていたルールが意外にもシンプルであることが分かりました。
少し簡単すぎたかもしれませんが、良い問題を思いついた方はcontactから連絡をいただければ幸いです。