Deep copy of objects in JS

Keywords: Front-end

In JS, the general = sign passes references to objects / arrays and does not really copy an object. How to make a deep copy of the object? If you have questions about this, this article may help you

1, The difference between object reference, shallow copy and deep copy

The object reference passing of js is very simple to understand. Refer to the following code:

var a = {name:'wanger'}
var b = a ;
a===b // true
b.name = 'zhangsan'
a.name //'zhangan'

In the above code, = is used for assignment, so B points to the stack object pointed to by a, that is, a and B point to the same stack object. Therefore, when assigning b.name, a.name also changes. To avoid the above situation, you can copy the object. The code is as follows:

var a = {name:'wanger'}
var b = Object.assign({}, a)
a===b // false
b.name = 'zhangsan'
a.name //'wanger'

The above code copies the original object to an empty object to obtain the clone of the original object. At this time, a and B point to different stack objects, so re copying b.name will not affect a.name. However, if a.name is a reference to an object rather than a string, the above code will also encounter some problems. Refer to the following code:

var a = {name:{firstName:'wang',lastName:'er'}}
var b = Object.assign({}, a)
a===b // false
b.name.firstName = 'zhang'
a.name.firstName //'zhang'

b.name.firstName also affects a.name.firstName. This is because the Object.assign() method is only a shallow copy. A.name is a reference to a stack object. When assigned to B, b.name is also a reference to the stack object. Many times, we don't want this to happen, so we need to use a deep copy of the object.

2, Copy the object using JSON.parse() and JSON.stringify()

Generally, we can use JSON.parse() and JSON.stringify() to realize deep cloning of objects, as follows:

var clone = function (obj) {
    return JSON.parse(JSON.stringify(obj));
}

This method is only applicable to the deep cloning of pure data json objects, because sometimes this method also has defects. Refer to the following code:

var clone = function (obj) {
    return JSON.parse(JSON.stringify(obj));
}
var a = {a:function(){console.log('hello world')},b:{c:1},c:[1,2,3],d:"wanger",e:new Date(),f:null,g:undefined}
var b = clone(a)

 

We find that the above method ignores the fields with function and undefied values, and the support for date type is not friendly.

More importantly, the above method can only clone the value of the original object itself, not the inherited value. Refer to the following code:

function Person (name) {
    this.name = name
}
var wanger = new Person('WangTwo ')
var newwanger = clone(wanger)
wanger.constructor === Person // true
newwanger.constructor === Object // true

 

We found that the constructor of the cloned Object has become Object, while the original Object is constructed as Person.

3, At present, there is no object deep copy method with bug found

Wang Er consulted many articles on the Internet, but the methods are not perfect. Therefore, he modified them on the basis of his predecessors. The methods are as follows. At present, no bug s have been found:

var clone = function (obj) { 
    if(obj === null) return null 
    if(typeof obj !== 'object') return obj;
    if(obj.constructor===Date) return new Date(obj); 
    var newObj = new obj.constructor ();  //Maintain inheritance chain
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {   //Does not traverse properties on its prototype chain
            var val = obj[key];
            newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // Use arguments.callee to decouple from the function name
        }
    }  
    return newObj;  
}; 

Here are three points to note:
1. Use the new obj.constructor () constructor to create an empty object instead of {} or [], which can maintain the inheritance of the prototype chain;
2. Use obj.hasOwnProperty(key) to judge whether the attribute comes from the prototype chain, because for..in.. will also traverse the enumerable attributes on its prototype chain.
3. The above function uses recursive algorithm. When the function has a name and the name will not change in the future, there is no problem with this definition. But the problem is that the execution of this function is closely coupled with the function name factorial. In order to eliminate this tight coupling, it is necessary to use   arguments.callee.

It was added on October 3, 2017. The problem of regular objects was not considered before. Here, make another modification:

var clone = function (obj) { 
    if(obj === null) return null 
    if(typeof obj !== 'object') return obj;
    if(obj.constructor===Date) return new Date(obj); 
    if(obj.constructor === RegExp) return new RegExp(obj);
    var newObj = new obj.constructor ();  //Maintain inheritance chain
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {   //Does not traverse properties on its prototype chain
            var val = obj[key];
            newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // Use arguments.callee to decouple from the function name
        }
    }  
    return newObj;  
}; 

Posted by www.WeAnswer.IT on Sat, 23 Oct 2021 23:26:47 -0700