Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

๐Ÿก this is a summary of deep copy when I read the relevant content, so I will try to do the most comprehensive collection, if you do not know what deep copy way, then let’s have a look! ๐Ÿ‘€ ๐Ÿ‘€

Why deep copy

It’s the same old story: before we can figure out how something works, we have to figure out why we need it;

Uh… Here I will not be so wordy (๐ŸŽˆ believe that you are mainly come to see the implementation method ๐ŸŽˆ)

Deep copy is compared to shallow copy. The main difference lies in the reference type. In essence, shallow copy simply copies the reference address in the stack, so when you change the value of the new copy, the copied object is also changed. Deep copy creates space in the heap for new objects, so copied objects can’t be modified for no reason.

So how do you implement deep copy? Please see below ๐Ÿ‘‡๐Ÿ‘‡๐Ÿ‘‡

How to implement deep copy

Object.assign

Object.assign by default, deep copies of objects will be assigned only to the outermost layer of objects.

function cloneDeepAssign(obj){
  return Object.assign({},obj)
  //Object.assign({},obj)
}
Copy the code

(Tip: Array copy method, using… , slice, concat, etc. copy the same effect, only deep copy the outermost layer)

Assign applies to enumerable attributes of the Object. Assign does not apply to unenumerable attributes.

Therefore, when we are dealing with a single hierarchical object, we can consider this method, simple and quick. (Tried, undefined is not supported)

JSONDeep copy of the implementation

This is one of the most commonly mentioned deep-copy methods, and most deep-copy methods can be done with JSON, essentially because JSON builds new memory to hold new objects.

function cloneDeepJson(obj){
  return JSON.parse(JSON.stringify(obj))
}
Copy the code

But we should pay attention to this:

  • ignoreundefinedandsymbol;
  • Can’t forFunctionMake a copy becauseJSONThe format string is not supportedFunctionIs automatically deleted during serialization.
  • Such asMap.Set.RegExp.Date.ArrayBuffer And other built-in types are lost in serialization;
  • Copies of circular reference objects are not supported; (Loop references can roughly be understood as the value of a property in an object being itself)

jQuerytheextend

var array = [1.2.3.4];
var newArray = $.extend(true,[],array);
Copy the code

Obviously, the biggest drawback is that we also need to introduce the jQuery library, so it is not very common; Interested friends, friends can give you directions – > [$. The extend (d], TGT, obj1, [objN]) | jQuery API 3.2 Chinese document | jQuery API online manual (cuishifeng. Cn)

MessageChannel

function deepCopy(obj) {
  return new Promise((resolve) = > {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev= > resolve(ev.data);
    port1.postMessage(obj);
  });
}

deepCopy(obj).then((copy) = > {/ / asynchronous
    let copyObj = copy;
    console.log(copyObj, obj)
    console.log(copyObj == obj)
});
Copy the code

(Personally, I feel this method is quite interesting, and if the interviewer tells it, it should give the interviewer a little surprise ๐Ÿ™Œ)

Cons: The biggest cons are asynchronous and can’t support Function

If you are interested in MessageChannel, please refer to the following article: – Jane books (jianshu.com)

The recursive implementation

function cloneDeepDi(obj){
  const newObj = {};
  let keys = Object.keys(obj);
  let key = null;
  let data = null;
  for(let i = 0; i<keys.length; i++){ key = keys[i]; data = obj[key];if(data && typeof data === 'object'){
      newObj[key] = cloneDeepDi(data)
    }else{ newObj[key] = data; }}return newObj
}
Copy the code

This is one of the most common solutions we use in interviews, so ask yourself, do you know it down?

Although but, it also has disadvantages, that is, it can not solve the problem of circular reference, once there is a circular reference, it is dead loop!

Solve recursive implementation of circular references

function deepCopy(obj, parent = null) {
    // Create a new object
    let result = {};
    let keys = Object.keys(obj),
        key = null,
        temp = null,
        _parent = parent;
    // If the field has a parent, you need to trace the parent of the field
    while (_parent) {
        // The field is a circular reference if it references its parent
        if (_parent.originalParent === obj) {
            // A circular reference returns a new object at the same level
            return _parent.currentParent;
        }
        _parent = _parent.parent;
    }
    for (let i = 0; i < keys.length; i++) {
        key = keys[i];
        temp = obj[key];
        // If the value of the field is also an object
        if (temp && typeof temp === 'object') {
            // Recursively perform deep copy to pass the same class of objects to be copied and the new object to parent for easy traceability of circular references
            result[key] = DeepCopy(temp, {
                originalParent: obj,
                currentParent: result,
                parent: parent
            });

        } else{ result[key] = temp; }}return result;
}
Copy the code

The general idea is to determine whether an object’s field refers to the object or any parent of the object. If the parent is referred to, the new object of the same level is returned directly, and if the parent is referred, the recursive process is carried out.

But in fact, there is a situation that is not solved, is the mutual reference between sub-objects, do not understand what the meaning of friends, you can see ->Javascript deep copy – Zhihu (zhihu.com) of the latter half of the content, corresponding to also write out the solution; (I am lazy, I will not repeat ๐Ÿคถ)

lodashthe_.cloneDeep()

var _ = require('lodash');
var obj1 = {
    a: 1.b: { f: { g: 1}},c: [1.2.3]};var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
Copy the code

This is the very, very best deep-copy approach, and it has taken into account all the possible problems, so it is recommended to use this mature solution in daily project development; On the principle of analysis, I am incompetent, can only point out the way for you:

Lodash

Lodash. CloneDeep | | lodash Chinese document lodash Chinese website (lodashjs.com)

Md at Master ยท Moyui /BlogPosts ยท GitHub

Note: The loDash solution to circular references is to use a stack to keep track of all copied references. If the same reference value is encountered again, it will not be copied again. ๐Ÿ–– ๐Ÿ––

conclusion

In fact, understanding the above way is already very enough; Keep in mind that in a daily production environment, the perfect solution — Lodash.clonedeep — is to use a recursive implementation if asked, supplemented by JSON, Object. Assgin, and MessageChannel. This is basically a good answer.

This is the end of the main content of this article, the following is to add some less commonly used methods, interested youyou can continue to understand

Add some less mainstream approaches

Object application of various methods

let deepClone = function (obj) {
    let copy = Object.create(Object.getPrototypeOf(obj));
    let propNames = Object.getOwnPropertyNames(obj);
    propNames.forEach(function (items) {
        let item = Object.getOwnPropertyDescriptor(obj, items);
        Object.defineProperty(copy, items, item);

    });
    return copy;
};
Copy the code

for.. in.withObject.createIn combination with implementation

function deepClone(initalObj, finalObj) {   
    var obj = finalObj || {};   
    for(var i in initalObj) {       
	var prop = initalObj[i];        // Avoid an infinite loop caused by cross-referencing objects, such as initalobj. a = initalObj
	if(prop === obj)  continue;      
	if(typeof prop === 'object') {
            obj[i] = (prop.constructor === Array)? [] :Object.create(prop);
        } else{ obj[i] = prop; }}return obj;
}
Copy the code

History API

Using history. ReplaceState. This API can change urls without refreshing when routing single page applications. This object uses structured cloning and is synchronous. However, we need to be careful not to mess up the routing logic in a single page. So when we clone an object, we need to restore the route to its original state.

function structuralClone(obj) {
   const oldState = history.state;
   const copy;
   history.replaceState(obj, document.title);
   copy = history.state;
   history.replaceState(oldState, document.title); 
   return copy;
}

var obj = {};
var b = {obj};
obj.b = b

var copy = structuralClone(obj);
console.log(copy);
Copy the code

The advantage of this method is that. It solves the problem of looping objects and supports cloning of many built-in types. And it’s synchronous. The downside is that some browsers limit the frequency of calls. Safari, for example, only allows 100 calls in 30 seconds

Notification API

This API is primarily used for desktop notifications. If you use Facebook, you’ll notice a popover in the bottom right corner of your browser, and that’s this guy. We can also use this API to make deep copies of JS objects.

function structuralClone(obj) { 
  return new Notification(' ', {data: obj, silent: true}).data;
}
var obj = {};
var b = {obj};
obj.b = b

var copy = structuralClone(obj);
console.log(copy)
Copy the code

It also has both advantages and disadvantages. The advantages are that it can solve the problem of looping objects, and it also supports many built-in types of cloning, and it is synchronous. The disadvantage is that this API requires user permission to use, but in this case, cloning data can be used without user permission. In the case of HTTP, you will be prompted to use HTTPS.

References:

3.1 Deep Copy and Shallow Copy – Front-end Internal Parameters (gitbook.io)

What are three ways to implement deep copy – FAQ -PHP Chinese Website