Enable deep copy

Deep and shallow copy

Shallow copy: only the reference (address) is copied. That is, after copying, the original variable and the new variable point to the same thing, and their operations will affect each other.
Deep copy: reallocate memory in the heap and have different addresses, but the values are the same. The copied object is completely isolated from the original object and does not affect each other.
The main difference between deep copy and shallow copy is whether the copy is a reference (address) or an instance.

Implementation without third-party libraries

For example, now we need to make a deep copy of the following object. How can we implement it without using a third-party library.

const source = {
    field1: 1,
    field2: undefined,
    field3: 'hello',
    field4: null,
    field4: {
        child: 'xiaoming',
        child2: {
            child2: 'ahong'
        }
    },
    fieldArray: [1, 2, 3, 4]
}

obj -> JsonString -> newObj

JSON.parse(JSON.stringify(source));

Hands on Implementation

  • Original data type, direct copy
  • Reference type, create a new object, and assign values to each attribute deep copy to the new object

Adhering to the above two directions, the first simple deep copy is realized.
1.1

function clone2(target){
    if(typeof target == 'object' && target != null){
        let cloneTarget = Array.isArray(target) ? [] : {}
        for(let key in target){
            cloneTarget[key] = clone2(target[key])
        }
        return cloneTarget
    }else{
        return target
    }
}
If this implementation references itself, source.XXX = source Stack overflow.

1.2
How to optimize it? Open up a new storage space to store the corresponding relationship between the current object and the copied object. When you need to copy the current object, first look in the storage space to see if you have copied this object.

function clone3(target, targetMap = new Map()){
    if(typeof target == 'object' && target != null){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        for(let key in target){
            cloneTarget[key] = clone3(target[key], targetMap)
        }
        return cloneTarget
    }else{
        return target
    }
}

Map is a strong reference. If the level of this reference is very high, the garbage collection mechanism will not actively help us recycle, and performance problems may occur. Then you can consider replacing the map with weakmap, which is the native data structure provided by ES6. As long as other references of the object are deleted, the garbage collection mechanism will release the memory occupied by the object, so as to avoid memory leakage.

function clone4(target, targetMap = new WeakMap()){
    if(typeof target == 'object' && target!=null){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        for(let key in target){
            cloneTarget[key] = clone4(target[key], targetMap)
        }
        return cloneTarget
    }else{
        return target
    }
}

1.3
As we all know, for in is slower than for loop and while, so change the loop.

function clone5(target, targetMap = new WeakMap()){
    if(typeof target == 'object'  && target!=null ){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        let keys = Array.isArray(target) ? null : Object.keys(target)
        let length = (keys || target).length
        let index = -1
        while(++index < length){
            let key = index
            if(keys){
                key = (keys || target)[index]
            }
            cloneTarget[key] = clone5(target[key], targetMap)
        }
        return cloneTarget
    }else{
        return target
    }
}

Pull out the while loop

function customForEach(array, iteratee) {
    let index = -1
    while(++index < array.length){
        iteratee(array[index], index)
    }
    return array
}

function clone6(target, targetMap = new WeakMap()){
    if(typeof target == 'object' && target!=null ){
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(targetMap.get(target)){
            return target
        }
        targetMap.set(target, cloneTarget)
        let keys = Array.isArray(target) ? null : Object.keys(target) 
        customForEach(keys || target, (value, key) => {
            if(keys){
                key = value
            }
            cloneTarget[key] = clone6(target[key], targetMap)
        })
        return cloneTarget
    }else{
        return target
    }
}

This is only a simple version, which is suitable for the deep copy of most business data. The type of function has not been considered. See the original text for details, or take a look at the source code of lodash

Keywords: Javascript

Added by rahnel on Mon, 07 Mar 2022 11:20:32 +0200