Detailed analysis of deep and shallow copies in JavaScript

  • 2021-08-21 19:39:04
  • OfStack

Before talking about deep and shallow copies in JS, we need to know something about the data types in JS, which can be divided into basic data types and reference data types. There is no deep and shallow copies for basic data types, but deep and shallow copies are mainly for reference data types.

1. Shallow copy

Shallow copies only copy references, not values. The simplest shallow copy in JS is implemented by using the "=" assignment operator.


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
  fun:function(){
    console.log('fun')
  }
}
var obj2 = obj1
obj2.a = 666 /*  Modify obj2 The value of, obj1 The value of also changes accordingly  */
console.log(obj1) /* {a: 666, b: Array(3), c: { … }, fun: ƒ} */

In the above code, we modified the value of obj2, and the value of obj1 changed accordingly, and only the shallow copy was realized with "=".

2. Deep copy

Deep copy is a complete copy of the target, and the two values after deep copy do not affect each other.

1. Using JSON. stringify and JSON. parse methods

JSON. stringify converts an JavaScript value to an JSON string;

JSON. parse converts an JSON string to an JavaScript value.


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
}			
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: { … }} */

Modifying the value of obj2 did not affect the attribute values in obj1, and obviously we implemented deep copying with JSON. parse and JSON. stringify.

But can it really be realized so simply? Let's take a look at the following example!


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
  fun:function(){
    console.log('fun')
  }
}			
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: { … }, fun: ƒ} */
console.log(obj2) /* {a: 12, b: Array(3), c: { … }} */

The attribute fun is missing from the converted obj2 because undefined, function and symbol are ignored in the conversion process using JSON. stringify. Obviously, we can't use this method to achieve deep copying when these types of attributes appear in our objects.

2. Recursion


function deepClone(source){
  if(!isObject(source)) return source
  var newObj = source instanceof Array? []:{}
  for(let key in source){
	if(source.hasOwnProperty(key)){
	  newObj[key] = isObject(source[key])?deepClone(source[key]):source[key]
    }
  }
  return newObj
}
function isObject(x) {
  return typeof x === 'object' && x != null
}

Test 1:


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
  fun:function(){
	console.log('fun')
  }
}			
var obj2 = deepClone(obj1)
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: { … }, fun: ƒ} */

As you can see from the example, we modified the value of the a attribute in obj2, but did not affect the value of the a attribute in obj1. We can achieve deep copy through recursion!

Note: The above method does not solve the problem of circular reference.


var obj1 = {}
obj1.a = obj1
var obj2 = deepClone(obj1) /*  Error reporting, stack overflow  */
console.log(obj2)

How to solve the circular reference problem and implement Symbol type copy will be improved later.

3. Other copying methods

1. concat () and slice () methods in arrays

We know that there are two methods in the array, concat and slice, which can copy the array and return the new array. Take concat as an example.


var arr = [1,2,3]
var arr2 = arr.concat()
arr2[2]=4
console.log(arr) /* [1, 2, 3] */
console.log(arr2) /* [1, 2, 4] */

Changing the value of arr2 does not affect the value of arr. Is this a deep copy of the array? Don't rush to draw conclusions first. Let's look at the following examples before analyzing:


var arr = [1,2,3,[4,5,6],{a:7}]
var arr2 = arr.concat()
arr2[3] = 444
arr2[4].a=8
console.log(arr) /* [1,2,3,[4,5,6],{a:8}] */
console.log(arr2) /* [1,2,3,444,{a:8}] */

When we modified arr2 [3] directly, it didn't cause arr to change, but when we modified arr2 [4]. a, the corresponding elements in arr changed with one time. In fact, when we change the elements in the arr2 array directly (for example, arr2 [0] =***, arr2 [1] =***, arr2 [3] =***), the original array arr will not be affected, but when we modify [3, 4, 5] or {a: 7} in the array, the original array arr will be changed.

Conclusion: concat () method makes a deep copy of the first layer of array.

You can try the array's slice () method again, which also makes only a deep copy of the array layer 1.

2. Object. assign () and... Expansion Operators


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'}
}
var obj2 = {...obj1}
obj2.a = 666
obj2.c.name = 'xinxin'
console.log(obj1) /* {a:1,b:[2,3,4],c:{name:'xinxin'}} */

You can see that what you implement with the... expansion operator is a deep copy of Layer 1 of the object. The following is only the copied reference value.

Try the Object. assign () method:


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'}
}
var obj2 = {}
Object.assign(obj2,obj1)
obj2.a = 666
obj2.b[0] = 0
console.log(obj1) /* {a:1,b:[0,3,4],c:{name:'tanj'} */

Similarly, only layer 1 of the object is deeply copied, and if the attribute value of the source object (for example, obj1) is a reference to the object, obj2 copies only that reference value. So when you change the array that b points to in obj2, the value of obj1 also changes.

We can achieve one such effect ourselves, and only make a deep copy of Layer 1:


var obj1 = {
  a:1,
  b:[2,3,4],
  c:{name:'tanj'},
}			
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 12
console.log(obj1) /* {a: 1, b: Array(3), c: { … }} */
0

The above is the analysis of JavaScript in depth copy details, more information about JavaScript depth copy please pay attention to other related articles on this site!


Related articles: