スプレッド構文がシャローコピーであることをしっかり理解できていなかったために業務中に落とし穴にはまりました。備忘録としてまとめます。
スプレッド構文とは?
スプレッド構文は、配列やオブジェクトの中身を展開する事ができます。
const numbers1 = [1, 2, 3]
const numbers2 = [...numbers1, 4, 5]
console.log(numbers2)
const fruites1 = {
apple: 'りんご',
banana: 'ばなな',
grape: 'ぶどう'
}
const fruites2 = {
...fruites1,
lemon: 'レモン',
orange: 'オレンジ'
}
console.log(fruites2)
※※※余談※※※
オブジェクトにおいて、プロパティ名が重複している場合は上書きされます。
const fruites1 = {
apple: 'りんご',
banana: 'ばなな',
grape: 'ぶどう'
}
const fruites2 = {
...fruites1,
apple: 'apple',
orange: 'オレンジ'
}
//fruites2で、fruites1を展開した後、fruites1のプロパティとして存在するappleを上書き
console.log(fruites2)
スプレッド構文はシャローコピー
本題です。
まず、以下の例において、実行結果は何になるでしょうか?
const numbers1 = [1, 2, 3]
const numbers2 = [...numbers1]
numbers2.push(4)
console.log(numbers1)
console.log(numbers2)
答えは、
となります。
次にオブジェクトの場合です。実行結果は何になるでしょうか?
const fruites1 = {
apple: 'りんご',
banana: 'ばなな',
orange: 'オレンジ'
}
const fruites2 = {...fruites1}
fruites2['grape'] = 'ぶどう'
console.log(fruites1)
console.log(fruites2)
この二つに共通して言えることは、スプレッド構文を用いて新しいオブジェクトを作成した場合、新しく作成したオブジェクトに変更を加えても元のオブジェクトには影響を与えない、ということです。
ただし、どんな状況でもこの考え方が適用できるかというと、そうではありません。
次の例をご覧ください。
const numbers1 = [
[1],
[2],
[3]
]
const numbers2 = [...numbers1]
numbers2[0].push(2)
console.log(numbers1)
console.log(numbers2)
この実行結果はどうなるでしょうか?
//number1 => [[1],[2],[3]]
//numbers => [[1, 2],[2], [3]]
となると思ってしまいそうすが、、、、こうはなりません!!
以下のようになります。
そうです、元の配列(fruites1)の内容が変更されてしまっています。
次にオブジェクトの場合も確認します。
const fruites1 = {
fruites: {
apple: 'りんご',
banana: 'ばなな',
orange: 'オレンジ'
},
vegitables: {
tomato: 'トマト',
eggplant: 'おなす'
}
}
const fruites2 = {...fruites1}
fruites2['fruites']['grape'] = 'ぶどう'
console.log(fruites1)
console.log(fruites2)
実行結果は以下になります。
こちらも配列と同様、元のオブジェクトが変更されてしまっています。
スプレッド構文を用いてオブジェクトや配列などを展開した場合、一階層の深さまでしかコピーされず、二階層以上は元のオブジェクトを参照します(シャローコピー)。
おわりに
今回取り上げたのはスプレッド構文でしたが、他にもオブジェクトを変更不可にするObject.freeze()もジャローコピーですし、シャローコピーについてはしっかり理解していないと思わぬバグの原因になってしまうので、今後も気を付けていきたいと思います。
参考文献:mdn web docs スプレッド構文