To answer this question, we need to clarify the following questions:

  • What is shallow copy?
  • What is deep copy?
  • What’s the difference?
  • How to do that?

2) ㅂ•́)و✧!

origin

First, we know that in JS variables are divided into two basic types and two reference types. When we assign a variable of an underlying type to another variable, we create a new block of memory to store it. When we assign a reference type to a new variable, we actually make both variables refer to the same block of memory.

If we modify objA at this point, it will affect objB:

Copying techniques are derived from modifying objA but affecting objB, which is obviously undesirable: When assigning, even if the target is a reference type, we create a new space in memory to copy the original data intact, so that we can modify the objA without affecting the objB.

Shallow copy

Shallow copy refers to copying data at a shallow level.

Going back to the above example, if you assign objA to objB with a shallow copy, then no matter how objA changes, it doesn’t affect objB.

But what if there are reference types in objA?

const objA = {
  name: 'Alice'.age: 18.family: {
    mother: 'Anna'.futher: 'Jonh',}}Copy the code

As we know, copying is to copy the data to the same place, so family will be copied to the same place, but because it is a reference type, it will copy the pointer to the same memory block. If family is modified, family in objB will obviously be affected.

Common shallow copy

Although shallow copy has the above hidden dangers, it is also a very common technology. In daily work, in addition to making wheels, there are the following methods to achieve shallow copy:

  • ES6 extension operator
  • Object.assign
  • Array.prototype.slice
  • Array.prototype.concat

Deep copy

Since shallow copy is a shallow copy of data, deep copy is a deep copy of data as the name implies. Whether the elements of the copied Object include Object, Array, Date, Promise, or even itself, the deep copy should be able to fully copy the data into the newly created memory space, so that any changes to the original Object will not affect the new Object:

What’s the difference

The difference mainly lies in the different levels of copy:

  • Shallow copy copies only the first layer of the data. For deep data, it simply uses assignment statements
  • Deep copy recursively traverses the entire object, making a full copy of all its data

How to implement

The core idea for deep copy is simple: iterate over the original object recursively, assigning values to the basic type at the minimum granularity.

At first glance, it doesn’t seem that deep copy is difficult to implement, but there are actually a few more things to consider:

  • Different types of processing:Object, Array, Function, RegExp, Date, Promise...
  • Circular reference:objA.me = objA

For circular reference, we can use weakMap< original object reference, new object reference > : if the current reference is judged to be equal to the original object reference in the next recursion, the reference of the newly created object can be returned directly.

For the processing of different objects, we only need to construct according to different objects, so we first assume that there is only Object in the Object, to implement the deep copy with circular reference, and then add the type.

function deepCopy(obj, me = new WeakMap(a)) {
  if(! isObject(obj))return obj
  if (me.get(obj)) return me.get(obj)

  let copy,
    result = {}
  me.set(obj, result)
  Object.keys(obj).forEach((key) = > {
    copy = obj[key]
    result[key] = isObject(copy) ? deepCopy(copy, me) : copy
  })
  return result
}
Copy the code

Let’s see if this code does what it needs to do:

Obviously, the most troublesome circular reference problem can be successfully solved by using WeakMap.

Of course, there are other ways to do this: maintain an array of two circular references, for example. But the simplest and most direct is WeakMap

Now that the core functionality is complete, it’s time to extend the type.

function isObject(obj) {
  return (typeof obj === 'object' || typeof obj === 'function') && obj ! = =null
}
function isArrary(obj) {
  return Array.isArray(obj)
}
function isDate(obj) {
  return Object.prototype.toString.call(obj) === '[object Date]'
}
function isSet(obj) {
  return Object.prototype.toString.call(obj) === '[object Set]'
}
function isMap(obj) {
  return Object.prototype.toString.call(obj) === '[object Map]'
}
function isRegExp(obj) {
  return Object.prototype.toString.call(obj) === '[object RegExp]'
}
function isFunction(obj) {
  return typeof obj === 'function'
}
function arrayCopy(obj) {
  const res = []
  for (const val of obj) {
    res.push(val)
  }
  return res
}
function dateCopy(obj) {
  const res = new Date(obj)
  return res
}
function setCopy(obj, me) {
  const res = new Set()
  obj.forEach((val) = > {
    res.add(deepCopy(val, me))
  })
  return res
}
function mapCopy(obj, me) {
  const res = new Map()
  obj.forEach((val, key) = > {
    res.set(key, deepCopy(val, me))
  })
  return res
}
function regExpCopy(obj) {
  function getRegExpInfo(r) {
    let flags = ' '
    if (r.global) flags += 'g'
    if (r.ignoreCase) flags += 'i'
    if (r.multiline) flags += 'm'
    return flags
  }
  const res = new RegExp(obj.source, getRegExpInfo(obj))
  if (obj.lastIndex) res.lastIndex = obj.lastIndex
  return res
}
function functionCopy(obj) {
  function createFunction(args, body) {
    if (body) {
      if (args) {
        const argsArr = args[0].split(', ')
        return new Function(... argsArr, body[0])}else {
        return new Function(body[0])}}}function createArrowsFunction(funcString) {
    return eval(funcString)
  }
  const bodyReg = / (? <={)(.|\n)+(? =})/m
  const argsReg = / (? < = \ () + (? =\)\s+{)/
  const funcString = obj.toString()

  const body = bodyReg.exec(funcString)
  const args = argsReg.exec(funcString)

  return obj.prototype ? createFunction(args, body) : createArrowsFunction(funcString)
}
function deepCopy(obj, me = new WeakMap(a)) {
  if(! isObject(obj))return obj
  if (me.get(obj)) return me.get(obj)

  let result, copy
  if (isArrary(obj)) {
    result = arrayCopy(obj)
    me.set(obj, result)
    return result
  }

  if (isDate(obj)) {
    result = dateCopy(obj)
    me.set(obj, result)
    return result
  }

  if (isSet(obj)) {
    result = setCopy(obj, me)
    me.set(obj, result)
    return result
  }

  if (isMap(obj)) {
    result = mapCopy(obj, me)
    me.set(obj, result)
    return result
  }

  if (isRegExp(obj)) {
    result = regExpCopy(obj)
    me.set(obj, result)
    return result
  }

  if (isFunction(obj)) {
    result = functionCopy(obj)
    me.set(obj, result)
    return result
  }

  result = {}
  me.set(obj, result)
  Object.keys(obj).forEach((key) = > {
    copy = obj[key]
    result[key] = isObject(copy) ? deepCopy(copy, me) : copy
  })
  return result
}
Copy the code

conclusion

003 – What is deep copy and what is the difference between shallow copy and deep copy