Deep copy implementation

This is the third day of my participation in Gwen Challenge

Introduction to the

The assignment of an object type actually copies the address, resulting in changes to one side and changes to the other. The solution to this problem is to use deep/shallow copies

Shallow copies can use extension operators directly (…) Or object. assign

Deep copy does not have a ready-made function

Json.parse (json.stringify (data)) is commonly used in business development for most deep-copy scenarios

But interviews generally look at using recursion to implement a deep copy

This paper will introduce the implementation scheme of each boundary case in detail

Common boundary problems

  • A circular reference
  • function
  • regular
  • The date of
  • symbol
  • Multiple key values refer to the same object, keeping the copied properties the same
  • .

1. Simple recursive implementation

Regardless of the boundary problem, elements only have value types, obj, arr

function deepClone(obj) {
    if(! isObject(obj))return obj
    if (Array.isArray(obj)) {
        const newObj = []
        for (const v of obj) {
            newObj.push(isObject(v) ? deepClone(v) : v)
        }
        return newObj
    }
    if (isObject(obj)) {
        const newObj = {}
        Object.keys(obj).forEach(k= > {
            const v = obj[k]
            newObj[k] = isObject(v) ? deepClone(v) : v
        })
        return newObj
    }
}

const a = {
    name: 'xiaoming'.id: 123131.info: {
        bd: '2020-01-01'.cards: [{
            q: 'q'.w: [1.2.3].e: { c: 'c'}}}}]console.log(JSON.stringify(deepClone(a)));
Copy the code

2. Solve circular references

It is well known that using JSON for deep copy does not solve the circular reference of an object, and an error will be reported if it occurs

You can solve this problem by using hash tables to record existing objects

Deepclone has been tweaked slightly above

function deepClone(obj) {
    const map = new WeakMap(a)const dp = (obj) = > {
        if(! isObject(obj))return obj
        // Resolve circular references
        if (map.has(obj)) return map.get(obj)
        map.set(obj, Array.isArray(obj) ? [] : {})

        if (Array.isArray(obj)) {
            const newObj = []
            for (const v of obj) {
                newObj.push(isObject(v) ? dp(v) : v)
            }
            return newObj
        }
        if (isObject(obj)) {
            const newObj = {}
            Object.keys(obj).forEach(k= > {
                const v = obj[k]
                newObj[k] = isObject(v) ? dp(v) : v
            })
            return newObj
        }
    }
    return dp(obj)
}

const b = {}, c = {}
b.next = c
c.next = b

console.log(deepClone(b)); // { next: { next: {} } }
Copy the code

3. Preserve the properties referenced by the original object

  • Store the copied object
  • Clone objects are returned directly
function deepClone(obj) {
    const map = new WeakMap(a)const dp = (obj) = > {
        if(! isObject(obj))return obj
        // Clone objects are returned directly
        if (map.has(obj)) return map.get(obj)
        // Resolve circular references
        map.set(obj, Array.isArray(obj) ? [] : {})

        if (Array.isArray(obj)) {
            const newObj = []
            for (const v of obj) {
                newObj.push(isObject(v) ? dp(v) : v)
            }
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
        if (isObject(obj)) {
            const newObj = {}
            Object.keys(obj).forEach(k= > {
                const v = obj[k]
                newObj[k] = isObject(v) ? dp(v) : v
            })
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
    }
    return dp(obj)
}

const obj = { a: 1 }
const t3 = { a: obj, d: obj, f: { g: obj } }
const tt3 = deepClone(t3)
console.log(tt3); // { a: { a: 1 }, d: { a: 1 }, f: { g: { a: 1 } } }
console.log(tt3.a === tt3.d); // true
console.log(tt3.a === tt3.f.g); // true
Copy the code

4. Copy Symbol

How to obtain the Symbol key of an object

There are several ways to get the key of an object

  • Reflect.ownKeys(target)The: method returns an array of the target object’s own property keys (Contains regular and Symbol keys)
  • Object.getOwnPropertySymbols(target): returns the value of a given object itselfAll Symbol attributesAn array of
  • Object.getOwnPropertyNames(target): returns an attribute name containing all the properties of the specified object (Includes non-enumerable propertiesbutDoes not include the Symbol value as a name attribute)
  • Object.keys(): Returns a value from a given objectSelf enumerable propertiesAn array of property names in the same order that is returned when the object is iterated through normally

To sum up

Reflect.ownKeys(target) 
/ / equivalent to the
Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Copy the code

Keys(targer); keys(targer); keys(Targer);

function deepClone(obj) {
    const map = new WeakMap(a)const dp = (obj) = > {
        if(! isObject(obj))return obj
        // Clone objects are returned directly
        if (map.has(obj)) return map.get(obj)
        // Resolve circular references
        map.set(obj, Array.isArray(obj) ? [] : {})

        if (Array.isArray(obj)) {
            const newObj = []
            for (const v of obj) {
                newObj.push(isObject(v) ? dp(v) : v)
            }
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
        if (isObject(obj)) {
            const newObj = {}
            // Use reflect.ownkeys instead
            Reflect.ownKeys(obj).forEach(k= > {
                const v = obj[k]
                newObj[k] = isObject(v) ? dp(v) : v
            })
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
    }
    return dp(obj)
}
const s1 = Symbol.for('s1')
const s2 = Symbol.for('s2')

const data = {
    [s1]: {
        name: 's1'.age: 19
    },
    [s2]: [1.2.'string', {
        title: s1
    }]
}
console.log(deepClone(data));
// { [Symbol(s1)]: { name: 's1', age: 19 },
// [Symbol(s2)]: [ 1, 2, 'string', { title: Symbol(s1) } ] }
Copy the code

5. Copy the special object Date/RegExp

For special objects, we can handle them in the following steps

  • Gets the constructor of the object
  • Checks if it is the specified special object
  • Call the constructor to generate a new object

Instantiated objects can be obtained from.constructor

Let’s modify the clone method above


function deepClone(obj) {
    const map = new WeakMap(a)const dp = (obj) = > {
        if(! isObject(obj))return obj
        // Clone objects are returned directly
        if (map.has(obj)) return map.get(obj)
        // Resolve circular references
        map.set(obj, Array.isArray(obj) ? [] : {})
        // Get the object's constructor
        const fn = obj.constructor
        // If it is regular
        if (fn === RegExp) {
            return new RegExp(obj)
        }
        // If it is a date
        if (fn === Date) {
            return new Date(obj.getTime())
        }

        if (Array.isArray(obj)) {
            const newObj = []
            for (const v of obj) {
                newObj.push(isObject(v) ? dp(v) : v)
            }
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
        if (isObject(obj)) {
            const newObj = {}
            // Use reflect.ownkeys instead
            Reflect.ownKeys(obj).forEach(k= > {
                const v = obj[k]
                newObj[k] = isObject(v) ? dp(v) : v
            })
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
    }
    return dp(obj)
}

const data = {
    today: new Date(),
    reg: /^abc$/ig
}
console.log(deepClone(data)); // {today: 2020-09-0t08:45:26.907z, reg: /^ ABC $/gi}
Copy the code

Copy function

Function copy program on the Internet collected a variety of, all kinds of strange skills, the following to give you a list of ha ha

  1. Using the eval:
    • Eval (fn.tostring ()) : Only arrow functions are supported
    • New Function(‘ return ‘+fn.toString())(): Cannot clone a Function and its original scope
  2. Fn.bind () : The new function returned can no longer use bind to change the this reference
// I'll simply use.bind

function deepClone(obj) {
    const map = new WeakMap(a)const dp = (obj) = > {
        if(! isObject(obj))return obj
        // Clone objects are returned directly
        if (map.has(obj)) return map.get(obj)
        // Resolve circular references
        map.set(obj, Array.isArray(obj) ? [] : {})
        // Get the object's constructor
        const fn = obj.constructor
        // If it is regular
        if (fn === RegExp) {
            return new RegExp(obj)
        }
        // If it is a date
        if (fn === Date) {
            return new Date(obj.getTime())
        }
        // if it is a function
        if (fn === Function) {
            return obj.bind({})
        }
        if (Array.isArray(obj)) {
            const newObj = []
            for (const v of obj) {
                newObj.push(isObject(v) ? dp(v) : v)
            }
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
        if (isObject(obj)) {
            const newObj = {}
            // Use reflect.ownkeys instead
            Reflect.ownKeys(obj).forEach(k= > {
                const v = obj[k]
                newObj[k] = isObject(v) ? dp(v) : v
            })
            // Store the copied object
            map.set(obj, newObj)
            return newObj
        }
    }
    return dp(obj)
}

const data = {
    today: new Date(),
    reg: /^abc$/ig,
    fn1: (a, b) = > {
        console.log(this.today);
        console.log(a + b);
    },
    fn2: function (a, b) {
        console.log(this.reg);
        console.log(a + b); }}const newData = deepClone(data)
newData.fn1(1.2) // undefined 3
newData.fn1.call({ today: '666' }, 1.2) // undefined 3
newData.fn2(3.4) // /^abc$/gi 7
newData.fn2.call({ reg: 123 }, 3.4) / / 123 7
const fn2 = newData.fn2
fn2.call({ reg: 'fn2Call' }, 2.3) // fn2Call 5
const fn3 = fn2.bind({ reg: 'string' })
fn3(2.3) // string 5
Copy the code

You can taste this article in detail for more details

For the full implementation of deep copy you can look at Lodash.clonedeep, source code