What is the difference between deep copy and shallow copy? How to implement a deep copy?

Posted by sBForum on Wed, 02 Mar 2022 05:56:26 +0100

1, Data type storage

As mentioned in the previous article, there are two major data types in JavaScript:

  • Basic type
  • reference type
    The basic type data is saved in stack memory
    The reference type data is stored in the heap memory. The variable of the reference data type is a reference to the actual object in the heap memory, which is stored in the stack

2, Shallow copy

Shallow copy refers to the creation of new data, which has an accurate copy of the attribute value of the original data.
If the attribute is a basic type, the value of the basic type is copied.
If the attribute is a reference type, the memory address is copied.
That is, the shallow copy is the copy layer, and the deep reference type is the shared memory address.
Achieve a simple shallow copy:

function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

In js, the phenomena of shallow copy include:

  • Object.assign
  • Array.prototype.slice(), Array.protortype.concat()
  • Replication using extension operators

Object.assign

Object.assign: copy the values of all enumerable attributes from one or more data source objects to the target object, and return the target object at the same time.

Object.assign(target,...sources)

Where target is the target object, source is the source object, which can be multiple, and the modification returns the target object target.
1. If the attribute in the target object has the same attribute key, the attribute will be overwritten by the attribute in the source object;
2. The dependency of the source object will similarly override the previous properties.

// saucxs
// First step
let a = {
    name: "wywy",
    age: 21
}
let b = {
    name: "xxx",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let c = Object.assign(a, b);
console.log(c);
// {
// 	name: "xxx",
//  age: 21,
// 	book: {title: "You Don't Know JS", price: "45"}
// }
console.log(a === c);
// true
 
// Step two
b.name = "change";
b.book.price = "55";
console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// }
 
// Step 3
console.log(a);
// {
// 	name: "saucxs",
//  age: 18,
// 	Book: {Title: "you don't know JS", price: "55"} price has changed
// }

analysis:
Step 1:
Use object Assign (a, b). Copy the value of source object B to target object A. here, define the return value as c. It can be seen that B will replace the value with the same key in A. that is, if the attribute in target object a has the same key, the attribute will be overwritten by the attribute in source object B. The returned object c is the target object a.
Step 2: Step 2: modify the basic type value (name) and reference type value (book) of the source object b.
Step 3: after shallow copy, the basic type value of object a does not change, but the reference type value changes because object Assign () copies the attribute value. The attribute value added to the source object is a reference to the object, and only the reference address is copied.

slice()

slice() is used to create a new array containing one or more elements in the original array, which will not affect the original array

const fxArr = ["One","Two","Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) //["One","love","Three"]
console.log(fxArrs) //["One","Two","Three"]

concat()

First, a copy of the current array will be created, then its parameters will be added to the end of the copy, and finally the newly constructed array will be returned without affecting the original array

const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

Extension operator

const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

3, Deep copy

Deep copy opens up a new stack. The completion of two objects is the same, but corresponding to two different addresses. Modifying the properties of one object will not change the properties of the other object.
Common deep copy methods include:

  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • Handwriting loop recursion

_.cloneDeep()

const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

JQuery.extend()

const $ = require('jquery');
const obj1 = {
	a:1,
	b:{f:{g:1}},
	c:[1,2,3]
} 
const obj2 = $.extend(true,{},obj1);
console.log(obj1.b.f === obj2.b.f); //false

JSON.stringify()

const obj2 = JSON.parse(JSON.stringify(obj1));

However, there are disadvantages in this method, which ignores undefined, symbol, and functions

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

Circular recursion

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // If it is null or undefined, I will not copy
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // It may be an object or ordinary value. If it is a function, it does not need deep copy
  if (typeof obj !== "object") return obj;
  // If it is an object, you need to make a deep copy
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // What is found is the constructor on the prototype of the class, and the constructor on the prototype points to the current class itself
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // Implement a recursive copy
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

4, Distinction

Illustration:

It can be seen from the above figure that both shallow copy and deep copy create a new object, but the behavior is different when copying object attributes.
Shallow copy only copies the pointer to an object, not the object itself. The old and new objects still share the same block of memory. Modifying the object properties will affect the original object.
However, deep copy will create another identical object. The new object does not share memory with the original object, and modifying the new object will not change to the original object.

5, Summary

When the copy type is reference type:
Shallow copy copies one layer. When the attribute is object, shallow copy copies. The two objects point to the same address
Deep copy is a recursive deep copy. When the attribute is an object, deep copy is a new stack, and the two objects point to different addresses.

Topics: Javascript Front-end