preface

When I was interviewed by a company, the interviewer asked me a question :” how to deep copy an object “. At that time my in the mind some secretly happy, so simple question still use to think? Parse (json.parse (obj)), or for… In plus recursively complete “. The interviewer nodded and felt satisfied. At that time, I didn’t care too much about this problem until I thought about it again some time ago and found that both of the above methods have bugs.

Ask questions

So what are these bugs?

  • Special object copy

First, let’s imagine an object that, regardless of the normal type, has the following members:


const obj = {
    arr: [111, 222],
    obj: {key: 'object'},
    a: () => {console.log('function'}, date: new date (), reg: / re /ig}Copy the code

Then we copy it in both ways

JSON method

JSON.parse(JSON.stringify(obj))

Copy the code

Output results:

As you can see, ordinary objects and arrays in obj can be copied, but the date object becomes a string, the function disappears, and the re becomes an empty object.

Let’s look at for… In plus recursion

recursive

function isObj(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj ! == null }function deepCopy(obj) {
    let tempObj = Array.isArray(obj) ? [] : {}
    for(let key in obj) {
        tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key]
    }
    return tempObj
}

Copy the code

Results:

conclusion

Neither of these methods can copy an object of type date or reg.

  • ring
What is a ring?

A loop is a circular reference to an object that causes itself to become a closed loop, such as this one:


var a = {}

a.a = a

Copy the code

Using the above two methods will directly copy error

The solution

  • ring

A WeakMap structure can be used to store the object that has been copied. Each time when copying, first query the object from the WeakMap whether it has been copied. If it has been copied, then take out the object and return


function deepCopy(obj, hash = new WeakMap()) {
    if(hash.has(obj)) return hash.get(obj)
    let cloneObj = Array.isArray(obj) ? [] : {}
    hash.set(obj, cloneObj)
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}

Copy the code

Copy ring result:

  • A copy of a particular object

It is difficult to solve this problem, because there are too many types of objects that need special treatment, so I refer to the structured copy on THE MDN, and then combine the solution of the ring:

// Only date and reg types can be addedfunction deepCopy(obj, hash = new WeakMap()) {
    let cloneObj
    let Constructor = obj.constructor
    switch(Constructor){
        case RegExp:
            cloneObj = new Constructor(obj)
            break
        case Date:
            cloneObj = new Constructor(obj.getTime())
            break
        default:
            if(hash.has(obj)) return hash.get(obj)
            cloneObj = new Constructor()
            hash.set(obj, cloneObj)
    }
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}

Copy the code

Copy results:

The full version can be viewed for lodash deep copy

  • Copy of function

But structured copy on MDN still does not solve the function copy

So far, I’ve only thought of using the eval method to copy functions, but this only works for arrow functions. Fun (){} is an error

Copy a function to increase the function type

Copy the result

Wrong type

Afterword.

Deep copying JavaScript goes beyond the pits mentioned above. There are also problems such as how to copy properties on the prototype chain? How do I copy non-enumerable properties? How to copy Error objects, etc., will not be covered here.

However, it is recommended to use JSON method in the daily process, this method has covered most of the business needs, so do not need to complicate the simple things, but in the interview if the interviewer is stuck to the point of this question can absolutely show him a face.