🐥

JavaScript: 浅いコピーと深いコピー

2022/09/18に公開

JavaScriptにおける浅いコピー(Shallow copy)と深いコピー(Deep copy)について

参考ドキュメント
MDN Shallow copy
MDN Deep copy
MDN JSON stringify

コードをGitHubに上げてます。ご自由にお試しください。
https://github.com/yuuta-wata/ShallowCopyAndDeepCopy

浅いコピー(Shallow copy)

浅いコピーを行うと1階層のみコピーし、2階層目以降は参照した値を生成します。

スプレッド構文で行うと下記のようになります。

const profile = ['jon', { hobby: ['game', 'running'] }]
// 浅いコピーを作成、1階層のみコピーされます。
const profileCopy = [...profile]

この時、コピー先のprofileCopyのhobby値を書き換えると、コピー元のprofileのhobby値も変わってしまいます。
これは2階層目以降のコピー元を参照しているからです。
言い換えると2階層目以降のコピー元のメモリを共有しているということです。

// コピー先のhobby値を書き換えてもコピー元の値も変わる
profileCopy[1].hobby[0] = 'reading'

console.log('\nprofileCopyのhobbyをreadingに書き換える:', profileCopy);
// [ 'jon', { hobby: [ 'reading', 'running' ] } ]
console.log('コピー元も値は変わる:', profile);
// [ 'jon', { hobby: [ 'reading', 'running' ] } ]

もちろんメモリを共有しているので、コピー元のprofileを書き換えても同様にコピー先のprofileCopyも書き変わります。

// コピー元のhobby値を書き換えるとコピー先も変わる
profile[1].hobby[0] = 'cooking'
console.log('\nprofileのhobbyをcookinに書き換える:', profile);
// [ 'jon', { hobby: [ 'cooking', 'running' ] } ]
console.log('コピー先も値は変わる:', profileCopy);
// [ 'jon', { hobby: [ 'cooking', 'running' ] } ]

1階層の値はコピーされている値なので、値を書き換えてもコピー元、コピー先共に影響を与えません。
つまり、1階層はメモリの共有は行われていません。

// コピー元のjonはコピーされてるので、書き換えてもコピー先は変わらない
profile[0] = 'mai'
console.log('\nprofileのjonを書き換える:', profile);
// [ 'mai', { hobby: [ 'cooking', 'running' ] } ]
console.log('jonは参照ではなくコピーされてるので\nコピー元を書き換えてもコピー先の値は変わらない:', profileCopy);
// [ 'jon', { hobby: [ 'cooking', 'running' ] } ]

深いコピー

深いコピーは全ての階層をコピーします。
メモリを共有することはありません。

JSON.stringifyを使った深いコピーのやり方は下記のようになります。

const menu = ['coffee', {salad: ['chikin salad', 'tomato salad']}]
// 深いコピーを作成、全ての階層がコピーされます
const menuCopy = JSON.parse(JSON.stringify(menu))

この時、コピー先のsaladを書き換えてもコピー元に影響はありません。
メモリを共有してないので、書き変わらないということです。

menuCopy[1].salad[0] = 'caesar salad'
console.log('\nmenuCopyのsaladを書き換える:', menuCopy);
// [ 'coffee', { salad: [ 'caesar salad', 'tomato salad' ] } ]
console.log('コピー元に影響はない:', menu)
// [ 'coffee', { salad: [ 'chikin salad', 'tomato salad' ] } ]

同様にコピー元のcoffeeを書き換えても、コピー先に影響はありません。

menu[0] = 'drink'
console.log('\nmenuのcoffeeを書き換える:', menu);
// [ 'drink', { salad: [ 'chikin salad', 'tomato salad' ] } ]
console.log('コピー先に影響はない:', menuCopy);
// [ 'coffee', { salad: [ 'caesar salad', 'tomato salad' ] } ]

Discussion