What is shallow copy and deep copy

  • Copy: Copies the source object to the target object. There are two types: shallow copy and deep copy
  • Shallow copy: If the copied object has attribute values that are not basic types (that is, objects), the shallow copy copies the reference of the object rather than the object itself. After the copy is complete, the target object is changed, and the source object is also changed
  • Deep copy: Deep copy perfectly solves the problem of shallow copy. The target object is a brand new object. Changing the target object does not affect the source object

Shallow copy

Object.assign()

// Object attribute values are base types
console.log('-------- object property values are of base type --------')
const src1 = { a: 'aa' }
const target1 = Object.assign({}, src1)
// Outputs the source object
console.log('Source object:', src1)  // {a: "aa"}
// Prints the copied target object
console.log('Target object:', target1)  // {a: "aa"}
// Change the target object
target1.a = 'aaa'
console.log('Result after changing target object:')
// Outputs the changed target object
console.log('Target object:', target1)  // {a: "aaa"}
// Outputs the source object and finds that the source object has not been changed
console.log('Source object:', src1)  // {a: "aa"}

// The attribute value of the object is a non-base type
console.log('-------- object has an attribute value of non-base type --------')
const src2 = {flag: 'src2'.a: { b: 'bb'}}const target2 = Object.assign({}, src2)
// Outputs the source object
console.log('Source object:', src2)  // {flag: "src2", a: {b: "bb"}}
// Outputs the target object
console.log('Target object:', target2)  // {flag: "src2", a: {b: "bb"}}
// Change the target object
target2.flag = 'target2'
target2.a.b = 'bbb'
console.log('Result after changing target object:')
// Outputs the changed target object
console.log('Target object:', target2)  // {flag: "target2", a: {b: "bbb"}}
// Output the source object and find that the source object is changed
console.log('Source object:', src2)  // {flag: "src2", a: {b: "bbb"}}
Copy the code

inThe operator

// Copy objects through a for in loop
function copy (target, src) {
  for (let key in src) {
    // Filter out the attributes on the prototype chain and copy only the attributes and values of the SRC object itself
    if (src.hasOwnProperty(key)) {
      target[key] = src[key]
    }
  }
  return target
}
// Object attribute values are base types
console.log('-------- object property values are of base type --------')
const src1 = { a: 'aa' }
const target1 = copy({}, src1)
// Outputs the source object
console.log('Source object:', src1)  // {a: "aa"}
// Prints the copied target object
console.log('Target object:', target1)  // {a: "aa"}
// Change the target object
target1.a = 'aaa'
console.log('Result after changing target object:')
// Outputs the changed target object
console.log('Target object:', target1)  // {a: "aaa"}
// Outputs the source object and finds that the source object has not been changed
console.log('Source object:', src1)  // {a: "aa"}

// The attribute value of the object is a non-base type
console.log('-------- object has an attribute value of non-base type --------')
const src2 = {flag: 'src2'.a: { b: 'bb'}}const target2 = copy({}, src2)
// Outputs the source object
console.log('Source object:', src2)  // {flag: "src2", a: {b: "bb"}}
// Outputs the target object
console.log('Target object:', target2)  // {flag: "src2", a: {b: "bb"}}
// Change the target object
target2.flag = 'target2'
target2.a.b = 'bbb'
console.log('Result after changing target object:')
// Outputs the changed target object
console.log('Target object:', target2) // {flag: "target2", a: {b: "bbb"}}
// Output the source object and find that the source object is changed
console.log('Source object:', src2)  // {flag: "src2", a: {b: "bbb"}}
Copy the code

Deep copy

JSON

  • advantages

Javascript built-in methods, simple, good performance

  • disadvantages

  • Objects with attributes of undefined and functions are ignored
  • Since the underlying implementation of the method uses recursion, if the object has a circular reference, the stack will burst.
  • implementation

// The attribute value of the object directly on is an object of non-underlying type
const src = { flag: 'src'.a: { b: 'bb'}}console.log('Source object:', src)  // Source object: {flag: "SRC ", a: {b: "bb"}}
const target = JSON.parse(JSON.stringify(src))
console.log('Target object:', target) {flag: "SRC ", a: {b: "bb"}}
console.log('-------- Change target object --------')
target.flag = 'target'
target.a.b = 'bbb'
console.log('Target object:', target)  // Target: {flag: "target", a: {b: "BBB "}}
// Find that the source object is not changed
console.log('Source object:', src)  // Source object: {flag: "SRC ", a: {b: "bb"}}

// exception - undefined and function
const obj = { a: 'a'.b: undefined.c: function () { conole.log('c function')}}console.log('Source object:', obj)  // Source object: {a: "a", b: undefined, c: ƒ}
const copyObj = JSON.parse(JSON.stringify(obj))
// The b and C attributes of the object are missing
console.log('Copied target object:', copyObj)  // Copy target object: {a: "a"}

// Exception - The object has a circular reference
const objLoop = {}
const b = {objLoop}
objLoop.b = b
console.log('Source object:', objLoop) // {b: {objLoop: {b: {objLoop: {b: ... }}}}}
Uncaught TypeError: Converting circular structure to JSON
const copyObjLoop = JSON.parse(JSON.stringify(objLoop))

Copy the code

MessageChannel

  • advantages

JavaScript’s built-in API, simple and can copy objects with undefined property value, also can solve the problem of circular reference

  • disadvantages

  • An error occurs when an object has an attribute value that is a function
  • Methods are asynchronous
  • implementation

// Copy the method
function deepCopy (obj) {
  return new Promise((resolve, reject) = > {
    const {port1, port2} = new MessageChannel()
    port1.postMessage(obj)
    port2.onmessage = function(e) {
      resolve(e.data)
    }
  })
}

// Example 1, normal object
/ / the source object
let src = { flag: 'src'.a: { b: 'bb'}}// Target object
let target = {}
deepCopy(src).then(res= > {
  target = res
  console.log('Source object:', src)  // Source object: {flag: "SRC ", a: {b: "bb"}}
  console.log('Target object:', target) {flag: "SRC ", a: {b: "bb"}}
  console.log('-------- Change target object --------')
  target.flag = 'target'
  target.a.b = 'bbb'
  console.log('Target object:', target)  // Target: {flag: "target", a: {b: "BBB "}}
  // Find that the source object is not changed
  console.log('Source object:', src)  // Source object: {flag: "SRC ", a: {b: "bb"}}
})

// For example 2, the property value is undefined
const obj = { a: 'a'.b: undefined}
target = {}
deepCopy(obj).then(res= > {
  console.log('Source object:', obj)  // Source object: {a: "a", b: undefined, c: ƒ}
  target = res
  console.log('Copied target object:', target)  // Target object: {a: "a", b: undefined}
})

Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': function () {} could not be cloned.
/* const obj1 = {a: 'a', b: undefined, c: function () {}} target = {} deepCopy(obj1). Then (res => {console.log(' source object: ', obj) // Source object: {a: "a", b: undefined, c: ƒ} target = res console.log(' Copied target object: ', target)}) */

// Example 4, circular reference
const obj2 = {}
const b = {obj2}
obj2.b = b
target = {}
deepCopy(obj2).then(res= > {
  console.log('Source object:', obj2) // {b: {obj2: {b: {obj2: {b: ... }}}}}
  target = res
  console.log('Target object:', target) // {b: {obj2: {b: {obj2: {b: ... }}}}}
})

Copy the code

recursive

  • advantages

JSON ignores undefined and function attributes

  • disadvantages

Objects with circular references will still burst the stack

  • implementation

// Deep copy method
function deepCopy (obj) {
  const target = {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // Key is an enumerable property of the object itself
      if (Object.prototype.toString.call(obj[key]) === '[object Object]') {
        // Attribute value is object, recursive call
        target[key] = deepCopy(obj[key])
      } else {
        target[key] = obj[key]
      }
    }
  }
  return target
}

/ / sample
const obj1 = {a: 'a'.b: {c: 'cc'}}
console.log('Source object:', obj1) // Source object: {a: "a", b: {c: "cc"}}
const copyObj1 = deepCopy(obj1)
console.log('Copied target object:', copyObj1) // Copy object: {a: "a", b: {c: "cc"}}
console.log('----- Change the properties of the target object ---------')
copyObj1.b = 'bb'
console.log('Source object:', obj1) // Source object: {a: "a", b: {c: "cc"}}
console.log('Target object:', copyObj1) {a: "a", b: "bb"}

// For example 2, the object has a circular reference
const objLoop = {}
const b = {objLoop}
objLoop.b = b
console.log('Source object:', objLoop) // {b: {objLoop: {b: {objLoop: {b: ... }}}}}
Uncaught RangeError: Maximum Call Stack size exceeded
const copyObjLoop = deepCopy(objLoop)

Copy the code

Closure + recursion

  • instructions

Solve the problem of JSON mode and recursive mode, can realize the true deep copy

  • implementation

// Deep copy method
function deepCopy (copyObj) {
  // It is used to record the copied property value as the property of the object and the value of the property
  const cache = {}

  // Copy objects
  function copy (obj) {
    const target = {}
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        // Key is an enumerable property of the object itself
        if (Object.prototype.toString.call(obj[key]) === '[object Object]') {
          // Attribute values are objects
          if (cache[obj[key]]) {
            // Indicates that the property has been copied once, and is now copied again, indicating a circular reference
            target[key] = cache[obj[key]]
          } else {
            cache[obj[key]] = obj[key]
            target[key] = copy(obj[key])
          }
        } else {
          target[key] = obj[key]
        }
      }
    }
    return target
  }
  return copy(copyObj)
}


/ / sample
const obj1 = {a: 'a'.b: {c: 'cc'}}
console.log('Source object:', obj1) // Source object: {a: "a", b: {c: "cc"}}
const copyObj1 = deepCopy(obj1)
console.log('Copied target object:', copyObj1) // Copy object: {a: "a", b: {c: "cc"}}
console.log('----- Change the properties of the target object ---------')
copyObj1.b = 'bb'
console.log('Source object:', obj1) // Source object: {a: "a", b: {c: "cc"}}
console.log('Target object:', copyObj1) {a: "a", b: "bb"}

// For example 2, the object has a circular reference
const objLoop = {}
const b = {objLoop}
objLoop.b = b
console.log('Source object:', objLoop) // Source object: {b: {objLoop: {b: {objLoop: {b: {b:... }}}}}
const copyObjLoop = deepCopy(objLoop)
console.log('Target object:', copyObjLoop) // Target object: {b: {objLoop: {b: {objLoop: {b: {b:... }}}}}
console.log('----- Change target object ------')
copyObjLoop.b = 'bb'
console.log('Source object:', objLoop) // Source object: {b: {objLoop: {b: {objLoop: {b: {b:... }}}}}
console.log('Target object:', copyObjLoop) // Target object: {b: "bb"}

Copy the code

instructions

In real production environments, it is recommended to use mature libraries such as Lodash.clonedeep. This article only describes the deep copy and shallow copy of JS, and how to implement it