JavaScriptでの浅いコピーと深いコピーを作成する

このチュートリアルでは、ディープコピーとは何か、シャローコピーとは何かについて学習します。次に、「値による」と「参照による」の意味について学習します。その後、JavaScriptが浅いコピーを作成する場合と深い場合、および必要なときに配列とオブジェクトの深いコピーを作成する方法についても学習します。
 

2種類のコピー

JavaScriptや他のプログラミング言語でデータをコピーするのは簡単に見えます。まあ、それは実際にはあなたが思っているよりももっと複雑かもしれません。あなたが知らないかもしれないことは、すべてのコピーが同じではないということです。一部のコピーは、実際には実際のコピーでさえありません。これは特にプログラミングに当てはまります。

プログラミングには、2種類のコピーがあります。最初のものは「ディープ」コピーと呼ばれます。2番目は「浅い」コピーと呼ばれます。これらの用語のいずれかまたはいずれかに精通していない場合でも、心配する必要はありません。あなたは両方について、それらが何であるか、そしてそれらがどのように機能するかについて学びます。また、JavaScriptでデフォルトでどちらが使用されるか、およびもう一方がどのように使用されるかについても学習します。

ディープコピー

最初のタイプである「ディープ」コピーから始めましょう。ディープコピーは、何かをコピーすることを考えるときにおそらく考えるものです。クローンのように、その1:1のコピーです。ディープコピーを作成すると、オリジナルの完全なコピーが作成されます。オリジナルからすべてのプロパティを取得し、それらをコピーにコピーします。

コピーとオリジナルは同じプロパティを持っています。ただし、これらのプロパティ、またはこれらのプロパティを含むものは接続されていません。これは覚えておくべき最も重要なことです。プロパティ、およびオリジナルとコピーは接続されていないため、オリジナルを変更しても、その変更はそのコピーに影響を与えません。

オリジナルに加えた変更は、オリジナルのみを変更します。コピーは変更されません。これが当てはまる場合、作成したのはディープコピーです。

浅いコピー

それはディープコピーについてでした。次に、2番目のタイプである浅いコピーについて説明します。浅いコピーは基本的に深いコピーの反対です。はい、すべてのコピーはまだオリジナルの1:1コピーです。ただし、浅いコピーの場合は、オリジナルとコピーのプロパティが連動します。したがって、オリジナルを変更すると、コピーも変更されます。

同じことがコピーにも当てはまります。コピーを変更すると、それらの変更によって元のコピーも変更されます。すべてのプロパティやその他のものを含む何かをコピーし、いくつかのコピーを作成するとします。これらのコピーはすべて浅いコピーです。次に、これらのコピーの1つだけを変更すると、他のすべてのコピーと元のコピーも変更されます。

値および参照による

1つの浅いコピーを変更すると、その変更によって他のすべての浅いコピーと元のコピーも自動的に変更されるという考えは奇妙に思えるかもしれません。内部で何が起こっているのか、そして「価値によって」そして「参照によって」コピーするという考えを理解するとき、それはより意味をなすようになります。

プログラミングでは、ものを渡す、またはコピーする2つの方法があります。1つは値によるもので、もう1つは参照によるものです。何かを渡すか、値でコピーすると、そのコピー、つまりディープコピーが作成されます。参照によって何かを渡すかコピーすると、元の浅いコピーのエイリアスだけが作成されます。新しいコピーや新しいクローンを作成していません。

// By reference
// Box is original
// Shallow copy 1,  Shallow copy 2, Shallow copy 3
// are just aliases to Box

|---------|
|   Box   |
|---------|
  |   |  |-------------connected to the Box-----------|
  |   |---connected to the Box-----|                  |
|--------------------| |--------------------| |--------------------|
|   Shallow copy 1   | |   Shallow copy 2   | |   Shallow copy 3   |
|--------------------| |--------------------| |--------------------|


// By value
// Box is original
// Copy 1,  Copy 2, Copy 3 are real and independent,
// i.e. deep, copies fo the Box

|---------|
|   Box   |
|---------|

|------------| |------------| |------------|
|   Copy 1   | |   Copy 2   | |   Copy 3   |
|------------| |------------| |------------|

参照によって作成されたすべてのコピー、つまり浅いコピーは、単なるエイリアスです。これは、これらのコピーのいずれかを変更しても、実際にはそのコピーを変更していないことを意味します。オリジナル自体を変更しています。すべての浅いコピーは単なるエイリアスであり、オリジナルを操作するためのエイリアスであることを忘れないでください。エイリアスの変更とは、オリジナルを変更することを意味します。

これが、浅いコピーに加えた変更が他のコピーと元のコピーを自動的に変更する理由です。コピーに変更を加えるのではなく、オリジナルに変更を加えます。また、すべてのコピーはオリジナルの単なるエイリアスであるため、オリジナルの現在の形状と形式を反映する必要があります。

簡単に要約すると、「値による」とは、オリジナルの実際のコピーを作成することを意味します。コピーとオリジナルの両方が完全に独立しています。一方を変更しても、もう一方には影響しません。「参照による」とは、オリジナルのエイリアスを作成することを意味します。新しいコピーやクローンはありません。それを呼び出すために使用できるのは、元の名前と新しい名前またはエイリアスの1つだけです。

したがって、エイリアスを使用する場合は、元の名前を「呼び出す」別の名前を使用しているだけです。したがって、エイリアスを使用すると、元のファイルが変更されます。また、その逆も同様です。これは、常に元のファイルのみを使用しているためです。

JavaScript、データ、メモリ

これらの浅いコピーと深いコピー、値、参照、エイリアスはすべて混乱を招く可能性があります。これらの概念を理解しやすくするのは、JavaScriptでのメモリ割り当てがどのように機能するかについての基本的な考え方です。新しい変数を作成すると、JavaScriptはその変数の値のためにメモリ内にスポットを割り当てます。

その変数の値を変更するとどうなりますか?JavaScriptは、その変数の値が格納されている正しいメモリスポットまたはアドレスを見つけます。次に、その値を変更します。つまり、メモリ内の特定のスポットまたはアドレスが変更されます。その変数を使用するとき、またはコードでそれを参照するときにも同じことが起こります。

その場合、JavaScriptは、その変数の値が格納されている正しいメモリスポット、つまりアドレスを再度見つけて使用します。最後に、新しい変数を作成すると、JavaScriptはメモリ内にさらに別の場所を割り当て、新しい変数とその値をそこに格納します。したがって、メモリ内に3つ以上のスポットが割り当てられています。

// Create variable - JavaScript allocates new spot in memory for it
let myOriginalVariableOne = 'I am now allocated in memory.'


// Change the value of "myOriginalVariableOne"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariableOne" is stored and changes it
myOriginalVariableOne = 'I have been changed.'


// Reference "myOriginalVariableOne"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariableOne" is stored and returns it
console.log(myOriginalVariableOne)
// 'I have been changed.'


// Create another variable - JavaScript allocates another new spot in memory
let myOriginalVariableTwo = 'I am second variable allocated in memory.'


// Reference "myOriginalVariableTwo"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariableTwo" is stored and returns it
console.log(myOriginalVariableTwo)
// 'I am second variable allocated in memory.'

JavaScript、データ、メモリ、ディープコピー

ある変数、ディープコピーをコピーすることにしたとしましょう。この場合、JavaScriptはメモリに新しいスポットを割り当て、その新しい変数の値(元の値のコピー)をそこに格納します。このプロセスは、完全に新しい変数を作成するのと同じです。

その結果、2つの異なる変数と、メモリ内の2つの異なるスポットができました。これらのスポットは両方とも完全に独立しています。一方を変更しても、もう一方は変更されません。

// Deep copies are created by value
// Create variable - JavaScript allocates new spot in memory for it
let myOriginalVariable = 'I am now allocated in memory.'


// Create deep copy of "myOriginalVariable" - JavaScript also allocates new spot in memory for the new deep copy
let myDeepCopy = myOriginalVariable


// Reference "myOriginalVariable"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and returns it
// Note: JavaScript looks for memory spot for "myOriginalVariable"
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// 'I am now allocated in memory.'


// Reference "myDeepCopy"
// JavaScript finds correct spot in memory
// where the value of "myDeepCopy" is stored and returns it
// Note: JavaScript looks for memory spot for "myDeepCopy",
// not the original, "myOriginalVariable"
console.log(myDeepCopy)
// Value stored in memory for "myDeepCopy":
// 'I am now allocated in memory.'


// Change the value of "myOriginalVariable"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and changes it
myOriginalVariable = 'Update for the original is coming.'


// Reference "myOriginalVariable" again
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and returns it
// Note: JavaScript looks for memory spot for "myOriginalVariable"
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// 'Update for the original is coming.'


// Reference "myDeepCopy" again
// JavaScript finds correct spot in memory
// where the value of "myDeepCopy" is stored and returns it
// Note: JavaScript looks for memory spot for "myDeepCopy",
// not the original, "myOriginalVariable"
console.log(myDeepCopy)
// Value stored in memory for "myDeepCopy":
// 'I am now allocated in memory.'

JavaScript、データ、メモリ、浅いコピー

変数もコピーしたいとします。しかし今、あなたは浅いコピーを作成します。この瞬間に何が起こりますか?これで、JavaScriptはそのコピーのメモリに新しいスポットを割り当てません。代わりに、JavaScriptは、元の変数に割り当てられたメモリ内のスポットに接続された新しいエイリアスを作成します。

その結果、そのコピー(浅いコピー)を参照すると、JavaScriptは元の変数に割り当てられたメモリスポットを見つけ、そこに格納されている値を使用して好きなことを実行できるようになります。コピーがないことを忘れないでください。オリジナルとコピー/エイリアスの両方が同じメモリスポット、同じ値に接続されています。

// Shallow copies are created by reference
// Create variable - JavaScript allocates new spot in memory for it
let myOriginalVariable = {
  title: 'The Everything Store',
  author: 'Brad Stone',
  releaseDate: 'October 15, 2013',
  publisher: 'Hachette Audio'
}


// Create copy of "myOriginalVariable" - JavaScript does NOT
// allocate new spot in memory
// instead, it will new alias that is connected
// to the memory spot allocated for "myOriginalVariable"
let myShallowCopy = myOriginalVariable


// Reference "myOriginalVariable"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and returns it
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// {
//   title: 'The Everything Store',
//   author: 'Brad Stone',
//   releaseDate: 'October 15, 2013',
//   publisher: 'Hachette Audio'
// }


// Reference "myShallowCopy"
// "myShallowCopy" is a shallow copy/alias,
// there is no spot in memory
// so, JavaScript looks for correct spot in memory
// where the value of the original "myOriginalVariable" is stored and returns it
console.log(myShallowCopy)
// Value stored in memory also for "myOriginalVariable":
// {
//   title: 'The Everything Store',
//   author: 'Brad Stone',
//   releaseDate: 'October 15, 2013',
//   publisher: 'Hachette Audio'
// }


// Change the shallow copy
// Since "myShallowCopy" is a shallow copy/alias,
// there is no spot in memory
// so, JavaScript looks for correct spot in memory
// where the value of the original "myOriginalVariable" is stored and changes that value
myShallowCopy.title = 'Creativity, Inc.'
myShallowCopy.author = 'Ed Catmull'
myShallowCopy.releaseDate = 'April 8, 2014',
myShallowCopy.publisher = 'Random House Audio'

// this is basically like
// myOriginalVariable.title = 'Creativity, Inc.'
// myOriginalVariable.author = 'Ed Catmull'
// myOriginalVariable.releaseDate = 'April 8, 2014',
// myOriginalVariable.publisher = 'Random House Audio'


// Reference "myOriginalVariable"
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// {
//   title: 'Creativity, Inc.',
//   author: 'Ed Catmull',
//   releaseDate: 'April 8, 2014',
//   publisher: 'Random House Audio'
// }


// Reference "myShallowCopy"
// Uses the same memory spot as "myOriginalVariable"
console.log(myShallowCopy)
// {
//   title: 'Creativity, Inc.',
//   author: 'Ed Catmull',
//   releaseDate: 'April 8, 2014',
//   publisher: 'Random House Audio'
// }


// Change the original
myOriginalVariable.title = 'Shoe dog'
myOriginalVariable.author = 'Phil Knight'
myOriginalVariable.releaseDate = 'April 26, 2016',
myOriginalVariable.publisher = 'Simon & Schuster Audio'


// Reference "myOriginalVariable"
// Value stored on memory spot for "myOriginalVariable"
console.log(myOriginalVariable)
// {
//   title: 'Shoe dog',
//   author: 'Phil Knight',
//   releaseDate: 'April 26, 2016',
//   publisher: 'Simon & Schuster Audio'
// }


// Reference "myShallowCopy"
// Uses the same memory spot as "myOriginalVariable"
console.log(myShallowCopy)
// {
//   title: 'Shoe dog',
//   author: 'Phil Knight',
//   releaseDate: 'April 26, 2016',
//   publisher: 'Simon & Schuster Audio'
// }

JavaScriptでデータをコピーするためのデフォルト

価値によって、参照によって、浅くて深いコピーがどのように機能するかについて、あなたがいくつかの考えを持っていることを願っています。それでは、落とし穴があるため、JavaScriptがコピーを処理する方法を見てみましょう。欠点は、JavaScriptが浅いコピーと深いコピーの両方を使用することです。

現時点でどのJavaScriptを使用するかを決定するのは、プリミティブデータ型またはオブジェクトとデータコレクションのいずれかで使用しているデータ型です。

プリミティブデータ型

プリミティブデータ型(数値、文字列、ブール値など)のコピーに関しては、JavaScriptは常にディープコピーを作成します。したがって、これらのデータ型の1つである値を持つ新しい変数を作成し、それをコピーする場合、何も心配する必要はありません。すべてのコピーにはメモリ内に独自の場所があり、誤って一方を変更して他方を変更することはできません。

// Primitive data types create deep copies by default

// Create variable containing a string
let myOriginalString = 'Let\'s create new memory spot.'


// Create a copy of myOriginalString (deep copy)
let myStringDeepCopy = myOriginalString


// Log the value of "myStringDeepCopy"
console.log(myStringDeepCopy)
// 'Let\'s create new memory spot.'


// Change the value of "myOriginalString"
myOriginalString = 'This will not change the deep copy.'


// Log the value of "myOriginalString"
console.log(myOriginalString)
// 'This will not change the deep copy.'


// Log the value of "myStringDeepCopy"
console.log(myStringDeepCopy)
// 'Let\'s create new memory spot.'


// Change the value of "myStringDeepCopy"
myStringDeepCopy = 'This will not change the original.'


// Log the value of "myStringDeepCopy"
console.log(myStringDeepCopy)
// 'This will not change the original.'

オブジェクト

オブジェクトの場合、状況は異なります。JavaScriptでは、オブジェクトは作成時に1回だけ保存されます。それらのいずれかをコピーすると、新しいコピーやディープコピーは作成されません。代わりに、JavaScriptは、オリジナルの単なるエイリアスである浅いコピーを作成します。メモリ内には、元のコピーとすべてのコピーに対して、まだ1つのスポットしかありません。

// Objects create shallow copies by default

// Create an object
const usersOne = {
  tony: 'admin',
  joe: 'user',
  ricky: 'guest'
}


// Create copies of usersOne object (shallow copies)
const usersTwo = usersOne
const usersThree = usersOne


// Log values of usersOne
console.log('usersOne: ', usersOne)
// 'usersOne: ' { tony: 'admin', joe: 'user', ricky: 'guest' }


// Log values of usersTwo
console.log('usersTwo: ', usersTwo)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'guest' }


// Log values of usersThree
console.log('usersThree: ', usersThree)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'guest' }


// Change the value of ricky property in usersOne (original object)
usersOne.ricky = 'user'


// Log values of usersOne again
// The value of "usersOne" changed
console.log('usersOne: ', usersOne)
// 'usersOne: ' { tony: 'admin', joe: 'user', ricky: 'user' }


// Log values of usersTwo again
// The value of "usersTwo" changed
console.log('usersTwo: ', usersTwo)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user' }


// Log values of usersThree again
// The value of "usersThree" changed
console.log('usersThree: ', usersThree)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user' }


// Add another key/value pair to usersThree (shallow copy)
usersThree.jackie = 'guest'


// Log values of usersOne again
// The value of "usersOne" changed again
console.log('usersOne: ', usersOne)
// 'usersOne: ' { tony: 'admin', joe: 'user', ricky: 'user', jackie: 'guest' }


// Log values of usersTwo again
// The value of "usersTwo" changed again
console.log('usersTwo: ', usersTwo)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user', jackie: 'guest' }


// Log values of usersThree again
// The value of "usersThree" changed again
console.log('usersTwo: ', usersThree)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user', jackie: 'guest' }

これが、オブジェクトを操作するときに注意を払う必要がある理由です。あなたはいつもオリジナルで作業しているので、あなたが望まないかもしれない何かをするのは簡単です。

配列

配列はオブジェクトと同じように機能します。新しい配列を作成すると、JavaScriptはそれを特定のメモリスポットに保存します。そのアレイのコピーまたは複数のコピーを作成する場合、すべてのコピーは、元のアレイに割り当てられたメモリ位置の単なるエイリアスになります。したがって、コピーまたはオリジナルを変更すると、変更はどこでも発生します。

// Arrays create shallow copies by default

// Create an array
let myOriginalArray = [1, 2, 'three', true]

// Create a copy of myOriginalArray (shallow copy)
let myShallowCopyArray = myOriginalArray


// Log the value of "myShallowCopyArray"
console.log(myShallowCopyArray)
// [ 1, 2, 'three', true ]


// Change the content of "myOriginalArray"
myOriginalArray[2] = 11
myOriginalArray.push(false)


// Log the value of "myOriginalArray" again
// The value of "myOriginalArray" changed
console.log(myOriginalArray)
// [ 1, 2, 11, true, false ]


// Log the value of "myShallowCopyArray" again
// The value of "myShallowCopyArray" also changed
console.log(myShallowCopyArray)
// [ 1, 2, 11, true, false ]


// Change the content of "myShallowCopyArray"
myShallowCopyArray.pop()
myShallowCopyArray.push(13)


// Log the value of "myOriginalArray"
// The value of "myOriginalArray" changed
console.log(myOriginalArray)
// [ 1, 2, 11, true, 13 ]


// Log the value of "myShallowCopyArray"
// The value of "myShallowCopyArray" also changed
console.log(myShallowCopyArray)
// [ 1, 2, 11, true, 13 ]

オブジェクトと同様に、配列を操作するときは注意が必要です。JavaScriptは配列の浅いコピーを作成するので、あなたが望まないかもしれない何かをするのは簡単です。

配列のディープコピーを作成する方法

配列と浅いコピーの問題を解決する簡単な方法の1つは、常に新しい配列を作成することです。これにより、浅いコピーではなく、常に深いコピーが作成されます。問題は、このアプローチも非常に面倒で、実際には効率的ではなく、維持するのが難しいことです。幸い、配列のディープコピーを作成するためのより良い方法があります。

注:Spread演算子は、配列にネストされたオブジェクトが含まれていない場合にのみ機能します。ネストされたオブジェクトがある場合、それらのネストされたオブジェクトは浅いコピーになります。したがって、元の配列のオブジェクトを変更すると、コピーされた配列内のオブジェクトも変更されます。その理由は、これらのオブジェクトの値が引き続き参照によってコピーされるためです。

ネストされたオブジェクトを含む配列を使用する場合はJSON.parse()、とを使用することをお勧めしますJSON.stringify()。これにより、ネストされたオブジェクトを含むディープコピーを作成できます。

スプレッド演算子

最初のオプションは、ES6で導入されたスプレッド演算子を使用することです。Spreadを使用すると、1つの配列を取得して、その値を新しい配列に「拡散」できます。その結果、同じコンテンツを持つ2つのアレイが作成され、どちらもメモリ内に独自に割り当てられたスポットがあります。したがって、一方を変更しても、もう一方は同じままです。

// Create an array
let myOriginalArray = ['Java', 'JavaScript']


// Create deep copy of myOriginalArray using spread
let myDeepCopyArray = [...myOriginalArray]


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Java', 'JavaScript' ]


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// [ 'Java', 'JavaScript' ]


// Change the content of "myOriginalArray"
myOriginalArray.push('C', 'C++')


// Log the value of "myOriginalArray"
// The original array, we changed, changed
console.log(myOriginalArray)
// [ 'Java', 'JavaScript', 'C', 'C++' ]


// Log the value of "myDeepCopyArray"
// The deep copy, we did NOT change, did NOT change
console.log(myDeepCopyArray)
// [ 'Java', 'JavaScript' ]


// Change the content of "myDeepCopyArray"
myDeepCopyArray.push('Python', 'Ruby')


// Log the value of "myDeepCopyArray"
// The deep copy, we changed, changed
console.log(myDeepCopyArray)
// [ 'Java', 'JavaScript', 'Python', 'Ruby' ]


// Log the value of "myOriginalArray"
// The original array, we did NOT change, did NOT change
console.log(myOriginalArray)
// [ 'Java', 'JavaScript', 'C', 'C++' ]

。スライス()

配列のディープコピーを作成するための別のオプションは、slice()メソッドを使用することです。このslice()メソッドは通常、配列の一部を返すために使用されます。ただし、これを使用して配列のディープコピーを作成することもできます。あなたがしなければならないのは、開始と終了の両方のパラメータを省略することです。空の括弧を使用するか、0を渡すことができます.slice(0)

// Create an array
let myOriginalArray = ['Doc', 'Marty']


// Create deep copy of myOriginalArray using .slice()
let myDeepCopyArray = myOriginalArray.slice()

// Creates the same result as using .slice(0):
// let myDeepCopyArray = myOriginalArray.slice(0)


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Doc', 'Marty' ]


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// [ 'Doc', 'Marty' ]


// Change the content of "myOriginalArray"
myOriginalArray.push('Einstein')


// Log the value of "myOriginalArray"
// The original array, we changed, changed
console.log(myOriginalArray)
// [ 'Doc', 'Marty', 'Einstein' ]


// Log the value of "myDeepCopyArray"
// The deep copy, we did NOT change, did NOT change
console.log(myDeepCopyArray)
// [ 'Doc', 'Marty' ]

マップ、フィルター、削減

ES6でも導入されたmap、filter、reduceメソッドは、配列のディープコピーを作成するのにも役立ちます。これが機能する理由は、の場合と同じslice()です。これらのメソッドはすべて配列を返します。したがって、それらを使用して配列(変更なし)を返し、それを変数に割り当てると、ディープコピーが作成されます。

//// Create an array
let myOriginalArray = ['Hard sci-fi', 'Soft sci-fi', 'Space opera']


// Create deep copy of myOriginalArray using .map()
// Map the original array and simply return all elements
let myMapDeepCopyArray = myOriginalArray.map(el => el)


// Create deep copy of myOriginalArray using .filter()
// Iterate over the original array and don't filter anything, just return every element
let myFilterDeepCopyArray = myOriginalArray.filter(el => el)


// Create deep copy of myOriginalArray using .reduce()
// Iterate over the original array and don't reduce it, just return the whole array (4th parameter)
let myReduceDeepCopyArray = myOriginalArray.reduce((accumulator, currentValue, currentIndex, array) => array)


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myMapDeepCopyArray"
console.log(myMapDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myFilterDeepCopyArray"
console.log(myFilterDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myReduceDeepCopyArray"
console.log(myReduceDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Change the original array
myOriginalArray.pop()
myOriginalArray.push('Social sci-fi')


// Log the value of "myOriginalArray" again
// The value did change, as we wanted
console.log(myOriginalArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Social sci-fi' ]


// Log the value of "myMapDeepCopyArray" again
// The value did NOT change
console.log(myMapDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myFilterDeepCopyArray" again
// The value did NOT change
console.log(myFilterDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myReduceDeepCopyArray" again
// The value did NOT change
console.log(myReduceDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]

Array.from()

を使用して、配列のディープコピーを作成することもできますArray.from()。この方法で配列のディープコピーを作成する場合は、変数を割り当てArray.from()、元の配列を.from()メソッドに渡します。結果は1:1のディープコピーになります。

// Create an array
let myOriginalArray = ['Action', 'Simulation']


// Create deep copy of myOriginalArray using .map()
// Map the original array and simply return all elements
let myDeepCopyArray = Array.from(myOriginalArray)


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Action', 'Simulation' ]


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// [ 'Action', 'Simulation' ]


// Change the value of "myOriginalArray"
myOriginalArray.push('RTS', 'Logic')


// Log the value of "myOriginalArray"
// The value did change, as we wanted
console.log(myOriginalArray)
// [ 'Action', 'Simulation', 'RTS', 'Logic' ]


// Log the value of "myDeepCopyArray"
// The value did NOT change
console.log(myDeepCopyArray)
//[ 'Action', 'Simulation' ]

JSON.parse()&JSON.stringify()

配列のディープコピーを作成するために、最後の、そしておそらく最も普遍的なオプションは使用しているJSON.parse()JSON.stringify()。何をするかというと、JSON.stringify()それは何かを文字列に変換するということです。次に、JSON.parse()それを元の形式またはデータ型に変換します。これを割り当てと組み合わせると、結果は新しい配列になります。

// Create an array
let myOriginalArray = ['video', 'audio']


// Create deep copy of myOriginalArray using JSON.parse() and JSON.stringify()
let myDeepCopyArray = JSON.parse(JSON.stringify(myOriginalArray))


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// ['video', 'audio']


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// ['video', 'audio']


// Change the "myOriginalArray"
myOriginalArray.push('VR', 'AR')
myOriginalArray.sort()


// Log the value of "myOriginalArray"
// Value has changed as we wanted
console.log(myOriginalArray)
// [ 'AR', 'VR', 'audio', 'video' ]


// Log the value of "myDeepCopyArray"
// Value is still the same, as we wanted
console.log(myDeepCopyArray)
// [ 'video', 'audio' ]

オブジェクトのディープコピーを作成する方法

配列と同様に、オブジェクトのディープコピーを作成する1つの方法は、オブジェクトをコピーするのではなく、新しいオブジェクトを作成することです。幸いなことに、より簡単で、より速く、煩わしくない他のオプションがあります。

注:配列と同様に、スプレッド演算子とネストされたオブジェクトの問題は、配列ではなくオブジェクトの場合にも発生します。ネストされたオブジェクトの場合、機能しない別のオブジェクト固有のオプションはObject.assign()です。これもネストされたオブジェクトの浅いコピーを作成します。幸いなことに、JSON.parse()そしてJSON.stringify()この問題を解決し、あなたは深いコピーを作成することができます。

Object.assign()

オブジェクトのディープコピーを作成するための最初のオプションはObject.assign()です。このassign()メソッドは、JavaScript開発者が、マージするオブジェクト、つまり「ターゲット」オブジェクトと「ソース」オブジェクトを提供することにより、オブジェクトをマージするためによく使用されます。ただし、このメソッドはオブジェクトのコピーにも使用できます。さらに重要なのは、ディープコピーを作成するためです。

このメソッドを使用してオブジェクトをコピーする場合は、2つのことを行う必要があります。まず、最初のパラメータである「ターゲット」として空のオブジェクトを渡す必要があります。次に、元のオブジェクトを「ソース」として渡す必要があります。最後に、assign()メソッドは新しいオブジェクトを返すため、それを変数に割り当てる必要があります。これにより、オブジェクトのディープコピーが作成されます。

// Create an object
let myOriginalObj = {
  language: 'English',
  difficulty: 'Easy'
}


// Create deep copy of "myOriginalObj" using Object.assign()
let myDeepCopyObj = Object.assign({}, myOriginalObj)


// Log the value of "myOriginalObj"
console.log(myOriginalObj)
// { language: 'English', difficulty: 'Easy' }


// Log the value of "myDeepCopyObj"
console.log(myDeepCopyObj)
// { language: 'English', difficulty: 'Easy' }


// Change the "myOriginalObj"
myOriginalObj.ethnicity = 'anglo-saxons'


// Log the value of "myOriginalObj"
// Value has changed as we wanted
console.log(myOriginalObj)
// {
//   language: 'English',
//   difficulty: 'Easy',
//   ethnicity: 'anglo-saxons'
// }


// Log the value of "myDeepCopyObj"
// Value is still the same, as we wanted
console.log(myDeepCopyObj)
// { language: 'English', difficulty: 'Easy' }

スプレッド演算子

配列と同様に、spread演算子を使用してオブジェクトのディープコピーを作成することもできます。

// Create an object
let myOriginalObj = {
  occupation: 'programmer',
  language: 'JavaScript'
}


// Create deep copy of myOriginalObj using Object.assign()
let myDeepCopyObj = { ...myOriginalObj }


// Log the value of "myOriginalObj"
console.log(myOriginalObj)
// { occupation: 'programmer', language: 'JavaScript' }


// Log the value of "myDeepCopyObj"
console.log(myDeepCopyObj)
// { occupation: 'programmer', language: 'JavaScript' }


// Change the "myOriginalObj"
myOriginalObj.language = ['JavaScript', 'TypeScript']


// Log the value of "myOriginalObj"
// Value has changed as we wanted
console.log(myOriginalObj)
// {
//   occupation: 'programmer',
//   language: [ 'JavaScript', 'TypeScript' ]
// }


// Log the value of "myDeepCopyObj"
// Value is still the same, as we wanted
console.log(myDeepCopyObj)
// { occupation: 'programmer', language: 'JavaScript' }

JSON.parse()&JSON.stringify()

配列の場合と同様に、オブジェクトを使用JSON.parse()JSON.stringify()てコピーすることもできます。アレイの場合と同じように機能します。JSON.stringify()オブジェクトを文字列に変換します。次に、JSON.parse()それを元のフォームまたはオブジェクトに変換し直します。それを何かに割り当てると、元のオブジェクトの深いコピーが得られます。

// Create an object
let myOriginalObj = {
  class: 'English',
  students: {
    ricky: {
      name: 'Ricky',
      grade: 'A+'
    },
    tommy: {
      name: 'Tommy',
      grade: 'B'
    }
  }
}


// Create deep copy of myOriginalObj using Object.assign()
let myDeepCopyObj = JSON.parse(JSON.stringify(myOriginalObj))


// Log the value of "myOriginalObj"
console.log(myOriginalObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' }
//   }
// }


// Log the value of "myDeepCopyObj"
console.log(myDeepCopyObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' }
//   }
// }


// Change the "myOriginalObj"
myOriginalObj.students.jimmy = {
  name: 'Jimmy',
  grade: 'B+'
}


// Log the value of "myOriginalObj"
// Value has changed as we wanted
console.log(myOriginalObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' },
//     jimmy: { name: 'Jimmy', grade: 'B+' }
//   }
// }


// Log the value of "myDeepCopyObj"
// Value is still the same, as we wanted
console.log(myDeepCopyObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' }
//   }
// }

結論:JavaScriptでの浅いコピーと深いコピーのしくみ

おめでとう。JavaScriptの浅いコピーと深いコピーに関するこのチュートリアルを終了しました。このチュートリアルを楽しんでいただけたでしょうか。要約すると、今日は、浅いコピーと深いコピーの2種類のコピーについて学習しました。また、「値による」と「参照による」の意味についても学びました。

次に、JavaScriptでデータとメモリがどのように機能するかについて少し学びました。その後、JavaScriptでデータをコピーするためのデフォルトについて学習しました。JavaScriptが浅いコピーを作成するときと深いとき。最後に、必要に応じて配列とオブジェクトのディープコピーを作成する方法も学びました。

この記事が気に入ったら、今後の投稿を見逃さないように購読してください。

リンク: https://blog.alexdevero.com/shallow-deep-copy-in-javascript/

#javascript 

What is GEEK

Buddha Community

JavaScriptでの浅いコピーと深いコピーを作成する

JavaScriptでの浅いコピーと深いコピーを作成する

このチュートリアルでは、ディープコピーとは何か、シャローコピーとは何かについて学習します。次に、「値による」と「参照による」の意味について学習します。その後、JavaScriptが浅いコピーを作成する場合と深い場合、および必要なときに配列とオブジェクトの深いコピーを作成する方法についても学習します。
 

2種類のコピー

JavaScriptや他のプログラミング言語でデータをコピーするのは簡単に見えます。まあ、それは実際にはあなたが思っているよりももっと複雑かもしれません。あなたが知らないかもしれないことは、すべてのコピーが同じではないということです。一部のコピーは、実際には実際のコピーでさえありません。これは特にプログラミングに当てはまります。

プログラミングには、2種類のコピーがあります。最初のものは「ディープ」コピーと呼ばれます。2番目は「浅い」コピーと呼ばれます。これらの用語のいずれかまたはいずれかに精通していない場合でも、心配する必要はありません。あなたは両方について、それらが何であるか、そしてそれらがどのように機能するかについて学びます。また、JavaScriptでデフォルトでどちらが使用されるか、およびもう一方がどのように使用されるかについても学習します。

ディープコピー

最初のタイプである「ディープ」コピーから始めましょう。ディープコピーは、何かをコピーすることを考えるときにおそらく考えるものです。クローンのように、その1:1のコピーです。ディープコピーを作成すると、オリジナルの完全なコピーが作成されます。オリジナルからすべてのプロパティを取得し、それらをコピーにコピーします。

コピーとオリジナルは同じプロパティを持っています。ただし、これらのプロパティ、またはこれらのプロパティを含むものは接続されていません。これは覚えておくべき最も重要なことです。プロパティ、およびオリジナルとコピーは接続されていないため、オリジナルを変更しても、その変更はそのコピーに影響を与えません。

オリジナルに加えた変更は、オリジナルのみを変更します。コピーは変更されません。これが当てはまる場合、作成したのはディープコピーです。

浅いコピー

それはディープコピーについてでした。次に、2番目のタイプである浅いコピーについて説明します。浅いコピーは基本的に深いコピーの反対です。はい、すべてのコピーはまだオリジナルの1:1コピーです。ただし、浅いコピーの場合は、オリジナルとコピーのプロパティが連動します。したがって、オリジナルを変更すると、コピーも変更されます。

同じことがコピーにも当てはまります。コピーを変更すると、それらの変更によって元のコピーも変更されます。すべてのプロパティやその他のものを含む何かをコピーし、いくつかのコピーを作成するとします。これらのコピーはすべて浅いコピーです。次に、これらのコピーの1つだけを変更すると、他のすべてのコピーと元のコピーも変更されます。

値および参照による

1つの浅いコピーを変更すると、その変更によって他のすべての浅いコピーと元のコピーも自動的に変更されるという考えは奇妙に思えるかもしれません。内部で何が起こっているのか、そして「価値によって」そして「参照によって」コピーするという考えを理解するとき、それはより意味をなすようになります。

プログラミングでは、ものを渡す、またはコピーする2つの方法があります。1つは値によるもので、もう1つは参照によるものです。何かを渡すか、値でコピーすると、そのコピー、つまりディープコピーが作成されます。参照によって何かを渡すかコピーすると、元の浅いコピーのエイリアスだけが作成されます。新しいコピーや新しいクローンを作成していません。

// By reference
// Box is original
// Shallow copy 1,  Shallow copy 2, Shallow copy 3
// are just aliases to Box

|---------|
|   Box   |
|---------|
  |   |  |-------------connected to the Box-----------|
  |   |---connected to the Box-----|                  |
|--------------------| |--------------------| |--------------------|
|   Shallow copy 1   | |   Shallow copy 2   | |   Shallow copy 3   |
|--------------------| |--------------------| |--------------------|


// By value
// Box is original
// Copy 1,  Copy 2, Copy 3 are real and independent,
// i.e. deep, copies fo the Box

|---------|
|   Box   |
|---------|

|------------| |------------| |------------|
|   Copy 1   | |   Copy 2   | |   Copy 3   |
|------------| |------------| |------------|

参照によって作成されたすべてのコピー、つまり浅いコピーは、単なるエイリアスです。これは、これらのコピーのいずれかを変更しても、実際にはそのコピーを変更していないことを意味します。オリジナル自体を変更しています。すべての浅いコピーは単なるエイリアスであり、オリジナルを操作するためのエイリアスであることを忘れないでください。エイリアスの変更とは、オリジナルを変更することを意味します。

これが、浅いコピーに加えた変更が他のコピーと元のコピーを自動的に変更する理由です。コピーに変更を加えるのではなく、オリジナルに変更を加えます。また、すべてのコピーはオリジナルの単なるエイリアスであるため、オリジナルの現在の形状と形式を反映する必要があります。

簡単に要約すると、「値による」とは、オリジナルの実際のコピーを作成することを意味します。コピーとオリジナルの両方が完全に独立しています。一方を変更しても、もう一方には影響しません。「参照による」とは、オリジナルのエイリアスを作成することを意味します。新しいコピーやクローンはありません。それを呼び出すために使用できるのは、元の名前と新しい名前またはエイリアスの1つだけです。

したがって、エイリアスを使用する場合は、元の名前を「呼び出す」別の名前を使用しているだけです。したがって、エイリアスを使用すると、元のファイルが変更されます。また、その逆も同様です。これは、常に元のファイルのみを使用しているためです。

JavaScript、データ、メモリ

これらの浅いコピーと深いコピー、値、参照、エイリアスはすべて混乱を招く可能性があります。これらの概念を理解しやすくするのは、JavaScriptでのメモリ割り当てがどのように機能するかについての基本的な考え方です。新しい変数を作成すると、JavaScriptはその変数の値のためにメモリ内にスポットを割り当てます。

その変数の値を変更するとどうなりますか?JavaScriptは、その変数の値が格納されている正しいメモリスポットまたはアドレスを見つけます。次に、その値を変更します。つまり、メモリ内の特定のスポットまたはアドレスが変更されます。その変数を使用するとき、またはコードでそれを参照するときにも同じことが起こります。

その場合、JavaScriptは、その変数の値が格納されている正しいメモリスポット、つまりアドレスを再度見つけて使用します。最後に、新しい変数を作成すると、JavaScriptはメモリ内にさらに別の場所を割り当て、新しい変数とその値をそこに格納します。したがって、メモリ内に3つ以上のスポットが割り当てられています。

// Create variable - JavaScript allocates new spot in memory for it
let myOriginalVariableOne = 'I am now allocated in memory.'


// Change the value of "myOriginalVariableOne"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariableOne" is stored and changes it
myOriginalVariableOne = 'I have been changed.'


// Reference "myOriginalVariableOne"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariableOne" is stored and returns it
console.log(myOriginalVariableOne)
// 'I have been changed.'


// Create another variable - JavaScript allocates another new spot in memory
let myOriginalVariableTwo = 'I am second variable allocated in memory.'


// Reference "myOriginalVariableTwo"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariableTwo" is stored and returns it
console.log(myOriginalVariableTwo)
// 'I am second variable allocated in memory.'

JavaScript、データ、メモリ、ディープコピー

ある変数、ディープコピーをコピーすることにしたとしましょう。この場合、JavaScriptはメモリに新しいスポットを割り当て、その新しい変数の値(元の値のコピー)をそこに格納します。このプロセスは、完全に新しい変数を作成するのと同じです。

その結果、2つの異なる変数と、メモリ内の2つの異なるスポットができました。これらのスポットは両方とも完全に独立しています。一方を変更しても、もう一方は変更されません。

// Deep copies are created by value
// Create variable - JavaScript allocates new spot in memory for it
let myOriginalVariable = 'I am now allocated in memory.'


// Create deep copy of "myOriginalVariable" - JavaScript also allocates new spot in memory for the new deep copy
let myDeepCopy = myOriginalVariable


// Reference "myOriginalVariable"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and returns it
// Note: JavaScript looks for memory spot for "myOriginalVariable"
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// 'I am now allocated in memory.'


// Reference "myDeepCopy"
// JavaScript finds correct spot in memory
// where the value of "myDeepCopy" is stored and returns it
// Note: JavaScript looks for memory spot for "myDeepCopy",
// not the original, "myOriginalVariable"
console.log(myDeepCopy)
// Value stored in memory for "myDeepCopy":
// 'I am now allocated in memory.'


// Change the value of "myOriginalVariable"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and changes it
myOriginalVariable = 'Update for the original is coming.'


// Reference "myOriginalVariable" again
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and returns it
// Note: JavaScript looks for memory spot for "myOriginalVariable"
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// 'Update for the original is coming.'


// Reference "myDeepCopy" again
// JavaScript finds correct spot in memory
// where the value of "myDeepCopy" is stored and returns it
// Note: JavaScript looks for memory spot for "myDeepCopy",
// not the original, "myOriginalVariable"
console.log(myDeepCopy)
// Value stored in memory for "myDeepCopy":
// 'I am now allocated in memory.'

JavaScript、データ、メモリ、浅いコピー

変数もコピーしたいとします。しかし今、あなたは浅いコピーを作成します。この瞬間に何が起こりますか?これで、JavaScriptはそのコピーのメモリに新しいスポットを割り当てません。代わりに、JavaScriptは、元の変数に割り当てられたメモリ内のスポットに接続された新しいエイリアスを作成します。

その結果、そのコピー(浅いコピー)を参照すると、JavaScriptは元の変数に割り当てられたメモリスポットを見つけ、そこに格納されている値を使用して好きなことを実行できるようになります。コピーがないことを忘れないでください。オリジナルとコピー/エイリアスの両方が同じメモリスポット、同じ値に接続されています。

// Shallow copies are created by reference
// Create variable - JavaScript allocates new spot in memory for it
let myOriginalVariable = {
  title: 'The Everything Store',
  author: 'Brad Stone',
  releaseDate: 'October 15, 2013',
  publisher: 'Hachette Audio'
}


// Create copy of "myOriginalVariable" - JavaScript does NOT
// allocate new spot in memory
// instead, it will new alias that is connected
// to the memory spot allocated for "myOriginalVariable"
let myShallowCopy = myOriginalVariable


// Reference "myOriginalVariable"
// JavaScript finds correct spot in memory
// where the value of "myOriginalVariable" is stored and returns it
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// {
//   title: 'The Everything Store',
//   author: 'Brad Stone',
//   releaseDate: 'October 15, 2013',
//   publisher: 'Hachette Audio'
// }


// Reference "myShallowCopy"
// "myShallowCopy" is a shallow copy/alias,
// there is no spot in memory
// so, JavaScript looks for correct spot in memory
// where the value of the original "myOriginalVariable" is stored and returns it
console.log(myShallowCopy)
// Value stored in memory also for "myOriginalVariable":
// {
//   title: 'The Everything Store',
//   author: 'Brad Stone',
//   releaseDate: 'October 15, 2013',
//   publisher: 'Hachette Audio'
// }


// Change the shallow copy
// Since "myShallowCopy" is a shallow copy/alias,
// there is no spot in memory
// so, JavaScript looks for correct spot in memory
// where the value of the original "myOriginalVariable" is stored and changes that value
myShallowCopy.title = 'Creativity, Inc.'
myShallowCopy.author = 'Ed Catmull'
myShallowCopy.releaseDate = 'April 8, 2014',
myShallowCopy.publisher = 'Random House Audio'

// this is basically like
// myOriginalVariable.title = 'Creativity, Inc.'
// myOriginalVariable.author = 'Ed Catmull'
// myOriginalVariable.releaseDate = 'April 8, 2014',
// myOriginalVariable.publisher = 'Random House Audio'


// Reference "myOriginalVariable"
console.log(myOriginalVariable)
// Value stored in memory for "myOriginalVariable":
// {
//   title: 'Creativity, Inc.',
//   author: 'Ed Catmull',
//   releaseDate: 'April 8, 2014',
//   publisher: 'Random House Audio'
// }


// Reference "myShallowCopy"
// Uses the same memory spot as "myOriginalVariable"
console.log(myShallowCopy)
// {
//   title: 'Creativity, Inc.',
//   author: 'Ed Catmull',
//   releaseDate: 'April 8, 2014',
//   publisher: 'Random House Audio'
// }


// Change the original
myOriginalVariable.title = 'Shoe dog'
myOriginalVariable.author = 'Phil Knight'
myOriginalVariable.releaseDate = 'April 26, 2016',
myOriginalVariable.publisher = 'Simon & Schuster Audio'


// Reference "myOriginalVariable"
// Value stored on memory spot for "myOriginalVariable"
console.log(myOriginalVariable)
// {
//   title: 'Shoe dog',
//   author: 'Phil Knight',
//   releaseDate: 'April 26, 2016',
//   publisher: 'Simon & Schuster Audio'
// }


// Reference "myShallowCopy"
// Uses the same memory spot as "myOriginalVariable"
console.log(myShallowCopy)
// {
//   title: 'Shoe dog',
//   author: 'Phil Knight',
//   releaseDate: 'April 26, 2016',
//   publisher: 'Simon & Schuster Audio'
// }

JavaScriptでデータをコピーするためのデフォルト

価値によって、参照によって、浅くて深いコピーがどのように機能するかについて、あなたがいくつかの考えを持っていることを願っています。それでは、落とし穴があるため、JavaScriptがコピーを処理する方法を見てみましょう。欠点は、JavaScriptが浅いコピーと深いコピーの両方を使用することです。

現時点でどのJavaScriptを使用するかを決定するのは、プリミティブデータ型またはオブジェクトとデータコレクションのいずれかで使用しているデータ型です。

プリミティブデータ型

プリミティブデータ型(数値、文字列、ブール値など)のコピーに関しては、JavaScriptは常にディープコピーを作成します。したがって、これらのデータ型の1つである値を持つ新しい変数を作成し、それをコピーする場合、何も心配する必要はありません。すべてのコピーにはメモリ内に独自の場所があり、誤って一方を変更して他方を変更することはできません。

// Primitive data types create deep copies by default

// Create variable containing a string
let myOriginalString = 'Let\'s create new memory spot.'


// Create a copy of myOriginalString (deep copy)
let myStringDeepCopy = myOriginalString


// Log the value of "myStringDeepCopy"
console.log(myStringDeepCopy)
// 'Let\'s create new memory spot.'


// Change the value of "myOriginalString"
myOriginalString = 'This will not change the deep copy.'


// Log the value of "myOriginalString"
console.log(myOriginalString)
// 'This will not change the deep copy.'


// Log the value of "myStringDeepCopy"
console.log(myStringDeepCopy)
// 'Let\'s create new memory spot.'


// Change the value of "myStringDeepCopy"
myStringDeepCopy = 'This will not change the original.'


// Log the value of "myStringDeepCopy"
console.log(myStringDeepCopy)
// 'This will not change the original.'

オブジェクト

オブジェクトの場合、状況は異なります。JavaScriptでは、オブジェクトは作成時に1回だけ保存されます。それらのいずれかをコピーすると、新しいコピーやディープコピーは作成されません。代わりに、JavaScriptは、オリジナルの単なるエイリアスである浅いコピーを作成します。メモリ内には、元のコピーとすべてのコピーに対して、まだ1つのスポットしかありません。

// Objects create shallow copies by default

// Create an object
const usersOne = {
  tony: 'admin',
  joe: 'user',
  ricky: 'guest'
}


// Create copies of usersOne object (shallow copies)
const usersTwo = usersOne
const usersThree = usersOne


// Log values of usersOne
console.log('usersOne: ', usersOne)
// 'usersOne: ' { tony: 'admin', joe: 'user', ricky: 'guest' }


// Log values of usersTwo
console.log('usersTwo: ', usersTwo)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'guest' }


// Log values of usersThree
console.log('usersThree: ', usersThree)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'guest' }


// Change the value of ricky property in usersOne (original object)
usersOne.ricky = 'user'


// Log values of usersOne again
// The value of "usersOne" changed
console.log('usersOne: ', usersOne)
// 'usersOne: ' { tony: 'admin', joe: 'user', ricky: 'user' }


// Log values of usersTwo again
// The value of "usersTwo" changed
console.log('usersTwo: ', usersTwo)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user' }


// Log values of usersThree again
// The value of "usersThree" changed
console.log('usersThree: ', usersThree)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user' }


// Add another key/value pair to usersThree (shallow copy)
usersThree.jackie = 'guest'


// Log values of usersOne again
// The value of "usersOne" changed again
console.log('usersOne: ', usersOne)
// 'usersOne: ' { tony: 'admin', joe: 'user', ricky: 'user', jackie: 'guest' }


// Log values of usersTwo again
// The value of "usersTwo" changed again
console.log('usersTwo: ', usersTwo)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user', jackie: 'guest' }


// Log values of usersThree again
// The value of "usersThree" changed again
console.log('usersTwo: ', usersThree)
// 'usersTwo: ' { tony: 'admin', joe: 'user', ricky: 'user', jackie: 'guest' }

これが、オブジェクトを操作するときに注意を払う必要がある理由です。あなたはいつもオリジナルで作業しているので、あなたが望まないかもしれない何かをするのは簡単です。

配列

配列はオブジェクトと同じように機能します。新しい配列を作成すると、JavaScriptはそれを特定のメモリスポットに保存します。そのアレイのコピーまたは複数のコピーを作成する場合、すべてのコピーは、元のアレイに割り当てられたメモリ位置の単なるエイリアスになります。したがって、コピーまたはオリジナルを変更すると、変更はどこでも発生します。

// Arrays create shallow copies by default

// Create an array
let myOriginalArray = [1, 2, 'three', true]

// Create a copy of myOriginalArray (shallow copy)
let myShallowCopyArray = myOriginalArray


// Log the value of "myShallowCopyArray"
console.log(myShallowCopyArray)
// [ 1, 2, 'three', true ]


// Change the content of "myOriginalArray"
myOriginalArray[2] = 11
myOriginalArray.push(false)


// Log the value of "myOriginalArray" again
// The value of "myOriginalArray" changed
console.log(myOriginalArray)
// [ 1, 2, 11, true, false ]


// Log the value of "myShallowCopyArray" again
// The value of "myShallowCopyArray" also changed
console.log(myShallowCopyArray)
// [ 1, 2, 11, true, false ]


// Change the content of "myShallowCopyArray"
myShallowCopyArray.pop()
myShallowCopyArray.push(13)


// Log the value of "myOriginalArray"
// The value of "myOriginalArray" changed
console.log(myOriginalArray)
// [ 1, 2, 11, true, 13 ]


// Log the value of "myShallowCopyArray"
// The value of "myShallowCopyArray" also changed
console.log(myShallowCopyArray)
// [ 1, 2, 11, true, 13 ]

オブジェクトと同様に、配列を操作するときは注意が必要です。JavaScriptは配列の浅いコピーを作成するので、あなたが望まないかもしれない何かをするのは簡単です。

配列のディープコピーを作成する方法

配列と浅いコピーの問題を解決する簡単な方法の1つは、常に新しい配列を作成することです。これにより、浅いコピーではなく、常に深いコピーが作成されます。問題は、このアプローチも非常に面倒で、実際には効率的ではなく、維持するのが難しいことです。幸い、配列のディープコピーを作成するためのより良い方法があります。

注:Spread演算子は、配列にネストされたオブジェクトが含まれていない場合にのみ機能します。ネストされたオブジェクトがある場合、それらのネストされたオブジェクトは浅いコピーになります。したがって、元の配列のオブジェクトを変更すると、コピーされた配列内のオブジェクトも変更されます。その理由は、これらのオブジェクトの値が引き続き参照によってコピーされるためです。

ネストされたオブジェクトを含む配列を使用する場合はJSON.parse()、とを使用することをお勧めしますJSON.stringify()。これにより、ネストされたオブジェクトを含むディープコピーを作成できます。

スプレッド演算子

最初のオプションは、ES6で導入されたスプレッド演算子を使用することです。Spreadを使用すると、1つの配列を取得して、その値を新しい配列に「拡散」できます。その結果、同じコンテンツを持つ2つのアレイが作成され、どちらもメモリ内に独自に割り当てられたスポットがあります。したがって、一方を変更しても、もう一方は同じままです。

// Create an array
let myOriginalArray = ['Java', 'JavaScript']


// Create deep copy of myOriginalArray using spread
let myDeepCopyArray = [...myOriginalArray]


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Java', 'JavaScript' ]


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// [ 'Java', 'JavaScript' ]


// Change the content of "myOriginalArray"
myOriginalArray.push('C', 'C++')


// Log the value of "myOriginalArray"
// The original array, we changed, changed
console.log(myOriginalArray)
// [ 'Java', 'JavaScript', 'C', 'C++' ]


// Log the value of "myDeepCopyArray"
// The deep copy, we did NOT change, did NOT change
console.log(myDeepCopyArray)
// [ 'Java', 'JavaScript' ]


// Change the content of "myDeepCopyArray"
myDeepCopyArray.push('Python', 'Ruby')


// Log the value of "myDeepCopyArray"
// The deep copy, we changed, changed
console.log(myDeepCopyArray)
// [ 'Java', 'JavaScript', 'Python', 'Ruby' ]


// Log the value of "myOriginalArray"
// The original array, we did NOT change, did NOT change
console.log(myOriginalArray)
// [ 'Java', 'JavaScript', 'C', 'C++' ]

。スライス()

配列のディープコピーを作成するための別のオプションは、slice()メソッドを使用することです。このslice()メソッドは通常、配列の一部を返すために使用されます。ただし、これを使用して配列のディープコピーを作成することもできます。あなたがしなければならないのは、開始と終了の両方のパラメータを省略することです。空の括弧を使用するか、0を渡すことができます.slice(0)

// Create an array
let myOriginalArray = ['Doc', 'Marty']


// Create deep copy of myOriginalArray using .slice()
let myDeepCopyArray = myOriginalArray.slice()

// Creates the same result as using .slice(0):
// let myDeepCopyArray = myOriginalArray.slice(0)


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Doc', 'Marty' ]


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// [ 'Doc', 'Marty' ]


// Change the content of "myOriginalArray"
myOriginalArray.push('Einstein')


// Log the value of "myOriginalArray"
// The original array, we changed, changed
console.log(myOriginalArray)
// [ 'Doc', 'Marty', 'Einstein' ]


// Log the value of "myDeepCopyArray"
// The deep copy, we did NOT change, did NOT change
console.log(myDeepCopyArray)
// [ 'Doc', 'Marty' ]

マップ、フィルター、削減

ES6でも導入されたmap、filter、reduceメソッドは、配列のディープコピーを作成するのにも役立ちます。これが機能する理由は、の場合と同じslice()です。これらのメソッドはすべて配列を返します。したがって、それらを使用して配列(変更なし)を返し、それを変数に割り当てると、ディープコピーが作成されます。

//// Create an array
let myOriginalArray = ['Hard sci-fi', 'Soft sci-fi', 'Space opera']


// Create deep copy of myOriginalArray using .map()
// Map the original array and simply return all elements
let myMapDeepCopyArray = myOriginalArray.map(el => el)


// Create deep copy of myOriginalArray using .filter()
// Iterate over the original array and don't filter anything, just return every element
let myFilterDeepCopyArray = myOriginalArray.filter(el => el)


// Create deep copy of myOriginalArray using .reduce()
// Iterate over the original array and don't reduce it, just return the whole array (4th parameter)
let myReduceDeepCopyArray = myOriginalArray.reduce((accumulator, currentValue, currentIndex, array) => array)


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myMapDeepCopyArray"
console.log(myMapDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myFilterDeepCopyArray"
console.log(myFilterDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myReduceDeepCopyArray"
console.log(myReduceDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Change the original array
myOriginalArray.pop()
myOriginalArray.push('Social sci-fi')


// Log the value of "myOriginalArray" again
// The value did change, as we wanted
console.log(myOriginalArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Social sci-fi' ]


// Log the value of "myMapDeepCopyArray" again
// The value did NOT change
console.log(myMapDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myFilterDeepCopyArray" again
// The value did NOT change
console.log(myFilterDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]


// Log the value of "myReduceDeepCopyArray" again
// The value did NOT change
console.log(myReduceDeepCopyArray)
// [ 'Hard sci-fi', 'Soft sci-fi', 'Space opera' ]

Array.from()

を使用して、配列のディープコピーを作成することもできますArray.from()。この方法で配列のディープコピーを作成する場合は、変数を割り当てArray.from()、元の配列を.from()メソッドに渡します。結果は1:1のディープコピーになります。

// Create an array
let myOriginalArray = ['Action', 'Simulation']


// Create deep copy of myOriginalArray using .map()
// Map the original array and simply return all elements
let myDeepCopyArray = Array.from(myOriginalArray)


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// [ 'Action', 'Simulation' ]


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// [ 'Action', 'Simulation' ]


// Change the value of "myOriginalArray"
myOriginalArray.push('RTS', 'Logic')


// Log the value of "myOriginalArray"
// The value did change, as we wanted
console.log(myOriginalArray)
// [ 'Action', 'Simulation', 'RTS', 'Logic' ]


// Log the value of "myDeepCopyArray"
// The value did NOT change
console.log(myDeepCopyArray)
//[ 'Action', 'Simulation' ]

JSON.parse()&JSON.stringify()

配列のディープコピーを作成するために、最後の、そしておそらく最も普遍的なオプションは使用しているJSON.parse()JSON.stringify()。何をするかというと、JSON.stringify()それは何かを文字列に変換するということです。次に、JSON.parse()それを元の形式またはデータ型に変換します。これを割り当てと組み合わせると、結果は新しい配列になります。

// Create an array
let myOriginalArray = ['video', 'audio']


// Create deep copy of myOriginalArray using JSON.parse() and JSON.stringify()
let myDeepCopyArray = JSON.parse(JSON.stringify(myOriginalArray))


// Log the value of "myOriginalArray"
console.log(myOriginalArray)
// ['video', 'audio']


// Log the value of "myDeepCopyArray"
console.log(myDeepCopyArray)
// ['video', 'audio']


// Change the "myOriginalArray"
myOriginalArray.push('VR', 'AR')
myOriginalArray.sort()


// Log the value of "myOriginalArray"
// Value has changed as we wanted
console.log(myOriginalArray)
// [ 'AR', 'VR', 'audio', 'video' ]


// Log the value of "myDeepCopyArray"
// Value is still the same, as we wanted
console.log(myDeepCopyArray)
// [ 'video', 'audio' ]

オブジェクトのディープコピーを作成する方法

配列と同様に、オブジェクトのディープコピーを作成する1つの方法は、オブジェクトをコピーするのではなく、新しいオブジェクトを作成することです。幸いなことに、より簡単で、より速く、煩わしくない他のオプションがあります。

注:配列と同様に、スプレッド演算子とネストされたオブジェクトの問題は、配列ではなくオブジェクトの場合にも発生します。ネストされたオブジェクトの場合、機能しない別のオブジェクト固有のオプションはObject.assign()です。これもネストされたオブジェクトの浅いコピーを作成します。幸いなことに、JSON.parse()そしてJSON.stringify()この問題を解決し、あなたは深いコピーを作成することができます。

Object.assign()

オブジェクトのディープコピーを作成するための最初のオプションはObject.assign()です。このassign()メソッドは、JavaScript開発者が、マージするオブジェクト、つまり「ターゲット」オブジェクトと「ソース」オブジェクトを提供することにより、オブジェクトをマージするためによく使用されます。ただし、このメソッドはオブジェクトのコピーにも使用できます。さらに重要なのは、ディープコピーを作成するためです。

このメソッドを使用してオブジェクトをコピーする場合は、2つのことを行う必要があります。まず、最初のパラメータである「ターゲット」として空のオブジェクトを渡す必要があります。次に、元のオブジェクトを「ソース」として渡す必要があります。最後に、assign()メソッドは新しいオブジェクトを返すため、それを変数に割り当てる必要があります。これにより、オブジェクトのディープコピーが作成されます。

// Create an object
let myOriginalObj = {
  language: 'English',
  difficulty: 'Easy'
}


// Create deep copy of "myOriginalObj" using Object.assign()
let myDeepCopyObj = Object.assign({}, myOriginalObj)


// Log the value of "myOriginalObj"
console.log(myOriginalObj)
// { language: 'English', difficulty: 'Easy' }


// Log the value of "myDeepCopyObj"
console.log(myDeepCopyObj)
// { language: 'English', difficulty: 'Easy' }


// Change the "myOriginalObj"
myOriginalObj.ethnicity = 'anglo-saxons'


// Log the value of "myOriginalObj"
// Value has changed as we wanted
console.log(myOriginalObj)
// {
//   language: 'English',
//   difficulty: 'Easy',
//   ethnicity: 'anglo-saxons'
// }


// Log the value of "myDeepCopyObj"
// Value is still the same, as we wanted
console.log(myDeepCopyObj)
// { language: 'English', difficulty: 'Easy' }

スプレッド演算子

配列と同様に、spread演算子を使用してオブジェクトのディープコピーを作成することもできます。

// Create an object
let myOriginalObj = {
  occupation: 'programmer',
  language: 'JavaScript'
}


// Create deep copy of myOriginalObj using Object.assign()
let myDeepCopyObj = { ...myOriginalObj }


// Log the value of "myOriginalObj"
console.log(myOriginalObj)
// { occupation: 'programmer', language: 'JavaScript' }


// Log the value of "myDeepCopyObj"
console.log(myDeepCopyObj)
// { occupation: 'programmer', language: 'JavaScript' }


// Change the "myOriginalObj"
myOriginalObj.language = ['JavaScript', 'TypeScript']


// Log the value of "myOriginalObj"
// Value has changed as we wanted
console.log(myOriginalObj)
// {
//   occupation: 'programmer',
//   language: [ 'JavaScript', 'TypeScript' ]
// }


// Log the value of "myDeepCopyObj"
// Value is still the same, as we wanted
console.log(myDeepCopyObj)
// { occupation: 'programmer', language: 'JavaScript' }

JSON.parse()&JSON.stringify()

配列の場合と同様に、オブジェクトを使用JSON.parse()JSON.stringify()てコピーすることもできます。アレイの場合と同じように機能します。JSON.stringify()オブジェクトを文字列に変換します。次に、JSON.parse()それを元のフォームまたはオブジェクトに変換し直します。それを何かに割り当てると、元のオブジェクトの深いコピーが得られます。

// Create an object
let myOriginalObj = {
  class: 'English',
  students: {
    ricky: {
      name: 'Ricky',
      grade: 'A+'
    },
    tommy: {
      name: 'Tommy',
      grade: 'B'
    }
  }
}


// Create deep copy of myOriginalObj using Object.assign()
let myDeepCopyObj = JSON.parse(JSON.stringify(myOriginalObj))


// Log the value of "myOriginalObj"
console.log(myOriginalObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' }
//   }
// }


// Log the value of "myDeepCopyObj"
console.log(myDeepCopyObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' }
//   }
// }


// Change the "myOriginalObj"
myOriginalObj.students.jimmy = {
  name: 'Jimmy',
  grade: 'B+'
}


// Log the value of "myOriginalObj"
// Value has changed as we wanted
console.log(myOriginalObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' },
//     jimmy: { name: 'Jimmy', grade: 'B+' }
//   }
// }


// Log the value of "myDeepCopyObj"
// Value is still the same, as we wanted
console.log(myDeepCopyObj)
// {
//   class: 'English',
//   students: {
//     ricky: { name: 'Ricky', grade: 'A+' },
//     tommy: { name: 'Tommy', grade: 'B' }
//   }
// }

結論:JavaScriptでの浅いコピーと深いコピーのしくみ

おめでとう。JavaScriptの浅いコピーと深いコピーに関するこのチュートリアルを終了しました。このチュートリアルを楽しんでいただけたでしょうか。要約すると、今日は、浅いコピーと深いコピーの2種類のコピーについて学習しました。また、「値による」と「参照による」の意味についても学びました。

次に、JavaScriptでデータとメモリがどのように機能するかについて少し学びました。その後、JavaScriptでデータをコピーするためのデフォルトについて学習しました。JavaScriptが浅いコピーを作成するときと深いとき。最後に、必要に応じて配列とオブジェクトのディープコピーを作成する方法も学びました。

この記事が気に入ったら、今後の投稿を見逃さないように購読してください。

リンク: https://blog.alexdevero.com/shallow-deep-copy-in-javascript/

#javascript