What is deep copy? What is shallow copy?

Everyone learning JS, hear others in the deep copy, shallow copy of the time will feel very curious, what is it? No hurry, let’s use an example to introduce the difference between shallow copy and deep copy.

Copy, as the name implies, is to make a copy, I have to do the same as you, this is a copy. Let’s copy it using the ES6 extension operator. As you can see, arR and ARR2 have the same content.

let obj = {
  name"jzsp".age20};let arr = [1.2.3, obj];
let arr2 = [...arr];
console.log(arr); //[ 1, 2, 3, { name: 'jzsp', age: 20 } ]
console.log(arr2); //[ 1, 2, 3, { name: 'jzsp', age: 20 } ]
Copy the code

But this is a shallow copy. What is a shallow copy? The fundamental difference between shallow and deep copies is the reference type of data. We all know that reference types are stored in the heap. In the example above, the array holds the address of a reference type, obj. If we modify obJ in ARR, let’s see what happens to ARR2.

let obj = {
  name"jzsp".age20};let arr = [1.2.3, obj];
let arr2 = [...arr];
arr[3].name = 'jzsp2 number'  
console.log(arr); //[1, 2, 3, {name: 'jzsp2 ', age: 20}]
console.log(arr2); //[1, 2, 3, {name: 'jzsp2 ', age: 20}]

\
Copy the code

As you can see, if you change the attributes of one reference, the other reference changes as well, because they essentially point to the same object. Something like the picture below (joker)

The meaning of deep copy is that I copy the reference type separately, so that if I change the reference of one object, the object of the other copy will not change, as shown in the following figure

Implementation of shallow copy method

.

. It’s not silent, it’s the ES6 expansion operator. The example looks like the one above me

let arr = [1.2.3];
let arr2 = [...arr];    // Shallow copy array

let obj = {
    name:'jzsp'.age:20
}

letobj2 = {... obj}// Shallow copy object
console.log(obj2)
Copy the code

Object.assign

let arr = [1.2.3];
let arr2 = Object.assign([],arr)  // Shallow copy array
console.log(arr2)   / / [1, 2, 3]


let obj = {
    name:'jzsp'.age:20
}

let obj2 = Object.assign({},obj)   // Shallow copy object
console.log(obj2)  //{ name: 'jzsp', age: 20 }
Copy the code

A way to achieve deep copy

JSON

We can convert an object to a JSON string using json.stringify (), and then convert a JSON string to an object using json.parse. As you can see from the figure below, deep copies are really unaffected, and shallow copies that modify the value of the reference type have an effect on the source object.

let obj = {
    name:'jzsp'.arr: [1.2.3.4]}let obj2 = {   / / shallow copy. obj }let obj3 = JSON.parse(JSON.stringify(obj))   / / copy

obj2.arr[0] = 3
console.log(obj)   
console.log(obj2)
console.log(obj3)
Copy the code

But JSON string has certain rules, and do not conform to the JSON TVC content will be ignored or even an error (undefined, the function, symbol), and the circular reference (between objects refer to each other, forming an infinite loop)

let obj = {
    name:undefined.foo:function(){
        console.log(81)},Symbol(123)]:'123'
}
letobj2 = { ... obj }let obj3 = JSON.parse(JSON.stringify(obj))

console.log(obj)   
console.log(obj2)
console.log(obj3)
Copy the code

As you can see, unsafe JSON values are ignored during copying.

So next, we need to implement a deep-copy function ourselves.

Implement the deep-copy function yourself

Self-implemented deep copy mainly needs to solve the following problems:

  • The function is ignored
  • Symbol is ignored
  • Undefined is ignored
  • A circular reference

1. Preliminary implementation of deep copy

let obj = {
    name:undefined.foo:function(){
       console.log(81)},Symbol(123)]:'123'.arr: [1.2.3.4]}// Check whether it is an object type
function isObject(obj){
    let type = typeof obj
    returnobj ! = =null && (type === "object" || type === "function");
}
function deepClone(val){
    // Val is the content to be copied. If the content to be copied is not an object type, just return it
    if(! isObject(val)){return val
    }

    // is the object type, so we create a new object, copy the content to this object and return it
    let newObj = {}
    for(const key in val){
        // Since we do not know whether the key attribute of val is a value or an object, we can recursively call the method to do the same for the value
        newObj[key] = deepClone(val[key])  
    }
    return newObj
}

let obj2 = deepClone(obj)
console.log(obj2)
console.log(obj)
Copy the code

2. Handle array types

As you can see, the properties of the array are copied as objects, so we need to judge the array.

function deepClone(val{
  if(! isObject(val)) {return val;
  }

  // If the value is an array, create an array, otherwise create an object
  let newObj = Array.isArray(val) ? [] : {};
  for (const key in val) {
    newObj[key] = deepClone(val[key]);
  }

  return newObj;
}
Copy the code

3. Process function objects

The contents of the function have been copied into an empty object. Here is a question: do we need to copy the function again? This is obviously not necessary and cannot be done, so we simply return the value when we determine that it is a function type

function deepClone(val{
  if(! isObject(val)) {return val;
  }
  // If it is a function object, return the function object directly
  if(typeof val === 'function'return val
  let newObj = Array.isArray(val) ? [] : {};
  for (const key in val) {
    newObj[key] = deepClone(val[key]);
  }
  return newObj;
}
Copy the code

4. Process Set and Map

Set and map are also objects, and they are also magically copied as empty objects, as shown below.

So we have to deal with these two special objects as well. If it is a set or map, create a new set or map.

function deepClone(val{
  if(! isObject(val)) {return val;
  }
  if(typeof val === 'function'return val
  // Determine the type by instanceof, and constructors can be passed to iterables
  if(val instanceof Setreturn new Set(val)
  if(val instanceof Mapreturn new Map(val)
  
  let newObj = Array.isArray(val) ? [] : {};
  for (const key in val) {
    newObj[key] = deepClone(val[key]);
  }
  return newObj;
}
Copy the code

5. Process Symbol

Symbol has two problems:

  • ifSymbolAs an attribute of an objectvalueIf so, then the returned object and the original object of this propertySymbolIs the sameBecause of itsisObject()isfalse, straight back.
  • SymbolObject keys are not copied because keys of type Symbol are not traversed by for

function deepClone(val{

  // If the value is of type symbol, copy the symbol descriptor
  // The symbol is not an object type and will be returned directly
  if(typeof val === 'symbol'return Symbol(val.description)
  if(! isObject(val)) {return val;
  }
  if(typeof val === 'function'return val
  if(val instanceof Setreturn new Set(val)
  if(val instanceof Mapreturn new Map(val)
  let newObj = Array.isArray(val) ? [] : {};
  for (const key in val) {
    newObj[key] = deepClone(val[key]);
  }

  // Get all properties of type symbol and make a deep copy of the values
  const symbolKeys = Object.getOwnPropertySymbols(val)
  for(const key of symbolKeys){
      newObj[key] = deepClone(val[key])
  }
  return newObj;
}

Copy the code

6. Handle circular references

Circular references mainly lead to recursive circular calls, which in turn lead to stack overflows

So we can save the copied object, and if we check whether the object has been copied before the next fetch, we can retrieve the object directly and return it. So here we’re going to save it with a map

// The default map is an empty map, so that the internal values of the function can be privatized

function deepClone(val,map = new Map(a){
  if(typeof val === 'symbol'return Symbol(val.description)
  if(! isObject(val)) {return val;
  }
  if(typeof val === 'function'return val
  if(val instanceof Setreturn new Set(val)
  if(val instanceof Mapreturn new Map(val)

  // If there is a previous copy of the content saved, return directly
  if(map.has(val)){
      return map.get(val)
  }
  let newObj = Array.isArray(val) ? [] : {};
  // Save a copy of the contents in the map
  map.set(val,newObj)
  for (const key in val) {
    newObj[key] = deepClone(val[key],map);
  }
  const symbolKeys = Object.getOwnPropertySymbols(val)
  for(const key of symbolKeys){
      newObj[key] = deepClone(val[key],map)
  }
  return newObj;
}
Copy the code

Now a self-implemented deep copy function is complete, thanks for watching.