About Deep Copy

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

Learn how to write a deep copy function by hand.

What is deep copy?

Let’s start with two small questions

Problem a:

let a = 1
b = a 
b = 2

// a = ?
Copy the code

Answer: A = 1

Problem two:

let a = { name: 'test' }
b = a
b.age = 18

// a.age = ?
Copy the code

Answer: A. age = 18

Why is that?

A = {name: ‘test’} a refers to the address of {name: ‘test’}. And b = a this operation, it is a point to address assigned to b, then a and b will point to the same address, will point to the {name: ‘test’} this object. And such a copy of the copy process, that is, shallow copy

So what if I want to modify object B, but not the value of object A? This requires the creation of a new memory space to store the objects pointed to by B, a process known as deep copy.

How to implement deep copy?

Easy way:

One of the simplest, and most commonly used, solutions to most problems is JSON serialization

const a = {name: 'test'}
const b = JSON.parse(JSON.stringify(a))

b.age = 18
console.log(a.age)// undefined
Copy the code

However, there is a problem with this method. If the value of a object has a function, undefined, Date, RegExp, then the result of JSON serialization will be lost, converted to another format, etc. JSON does not support these data types. From there, the need to write a deep copy by hand was introduced. Example:

Advanced methods

  • Let’s start by writing an implementation that copies common objects and primitive types
class DeepClone {
    clone(source) {
        if (source instanceof Object) {
            let dist = {}
            for(let key in source) {
                dist[key] = this.clone(source[key])
            }
            return dist
        } else {
            return source
        }
    }
}
Copy the code

Here we write a simple test case to make sure this method works

Describe ('DeepClone', () => {it(' he is a class ', () => {assert. IsFunction (DeepClone)}) it(' can copy basic types ', () => { const number = 123 const deepClone = new DeepClone() const number2 = deepClone.clone(number) assert(number === number2) const string = '123456' const string2 = deepClone.clone(string) assert(string === string2) const boolean1 = true const boolean2 = deepClone.clone(boolean1) assert(boolean1 === boolean2) const u = undefined const u2 = deepClone.clone(u) assert(u === u2) const empty = null const empty2 = deepClone.clone(empty) assert(empty === empty2) Clone (symbol) assert(symbol === symbol2)}) describe(' object ', () = > {it (' common object can be replicated, () = > {const a = {name: 'WWWBH' child: {name: 'XWWWBH, age: 1}} const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a ! == a2) assert(a.name === a2.name) assert(a.child ! == a2.child) assert(a.child.name === a2.child.name) assert(a.child.age === a2.child.age) }) }) })Copy the code

Running the test at this point shows that our function copies both ordinary types and simple objects with no problem

  • Next, add the Array, Function, RegExp, and Date types
class DeepClone { clone(source) { if (source instanceof Object) { let dist if (source instanceof Array) { dist = [] } else if (source instanceof Function) { dist = function() { return source.call(this, ... arguments) } } else if (source instanceof Date) { dist = new Date(source) } else if (source instanceof RegExp) { dist = new RegExp(source.source, source.flags) } else { dist = {} } for(let key in source) { dist[key] = this.clone(source[key]) } return dist } else { return source } } }Copy the code

Test case refinement:

describe('DeepClone', () => { // ... Describe (' objects' () = > {it (' common object can be replicated, () = > {const a = {name: 'WWWBH' child: {name: 'XWWWBH, age: 1}} const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a ! == a2) assert(a.name === a2.name) assert(a.child ! == a2.child) assert(a.c.child. Name === a2.child.name) assert(a.c.child. Age === a2.child.age)}) it(' can copy array objects ', () => { const a = [[11, 12], [21, 22], [31, 32]] const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a ! == a2) assert(a[0] ! == a2[0]) assert(a[1] ! == a2[1]) assert(a[2] ! == a2[2]) assert. DeepEqual (a, a2)}) it(' can copy function objects ', () => {const a = function (x, y) {return x + y} a.xx = {yyy: {zzz: 1}} const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a ! == a2) assert(a.xxx ! == a2.xxx) assert(a.xxx.yyy ! = = a2. XXX. Yyy) assert (a.x xx. Yyy. Z = = = a2. XXX, yyy, ZZZ) assert (a (1, 2) = = = a2 (1, 2))}) it (' can copy a regular, () => { const a = new RegExp('hi\d+', 'gi') a.xxx = {yyy: {zzz: 1}} const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a.source === a2.source) assert(a.flags === a2.flags) assert(a.xxx ! == a2.xxx) assert(a.xxx.yyy ! == a2.xxx.yyy) assert(a.xx.yyy.zzz === a2.xx.yyy.zzz)}) it(' can copy dates ', () => {const a = new Date() a.xx.yyy = {yyy: {zzz: 1}} const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a ! == a2) assert(a.getTime() === a2.getTime()) assert(a.xxx ! == a2.xxx) assert(a.xxx.yyy ! == a2.xxx.yyy) assert(a.xxx.yyy.zzz === a2.xxx.yyy.zzz) }) }) })Copy the code

Run our test case at this point

Once again the advanced

Currently DeepClone does not support ring references, which are data types like this:

Putting such a data type into our DeepClone would trigger a recursive loop that would burst the stack.

To solve this problem, we need a cache to store each source passed in, and compare the source with the cache to see if there are any objects that reference the same address. If there are none, we put them in the cache, and if there are, we return them. This solves the problem of ring references.

class DeepClone { cache = [] clone(source) { if (source instanceof Object) { let dist const cacheDist = this.findCache(source) if (cacheDist){ return cacheDist } else { if (source instanceof Array) { dist = [] } else if (source instanceof Function) { dist = function() { return source.call(this, ... arguments) } } else if (source instanceof Date) { dist = new Date(source) } else if (source instanceof RegExp) { dist = new RegExp(source.source, source.flags) } else { dist = {} } this.cache.push([source, dist]) for(let key in source) { dist[key] = this.clone(source[key]) } return dist } } else { return source } } findCache(source){ for(let i = 0; i < this.cache.length; i ++) { if (this.cache[i][0] === source) { return this.cache[i][1] } } } }Copy the code

Test case refinement:

describe('DeepClone', () => { // ... Describe (' object ', () => {//... It (' can copy the object referenced by the ring ', () => {const a = {name: 'wwwbh'} a.self = a const deepClone = new DeepClone() const a2 = deepClone.clone(a) assert(a ! == a2) assert(a.name === a2.name) assert(a.self ! == a2.self) }) }) })Copy the code

Run the test again at this point

So the deep copy of the ring reference object is also solved

Final evolution

Finally, in general, deep copy does not need to copy the method object on the prototype chain, so use the hasOwnProperty method to determine.

The final code

class DeepClone { cache = [] clone(source) { if (source instanceof Object) { let cachedDist = this.findCache(source) if (cachedDist) { return cachedDist } else { let dist if (source instanceof Object) { if (source instanceof Array) { dist =  [] } else if (source instanceof Function) { dist = function () { return source.call(this, ... arguments) } } else if (source instanceof RegExp){ dist = new RegExp(source.source, source.flags) } else if (source instanceof Date) { dist = new Date(source) }else { dist = {} } this.cache.push([source, Dist]) for (let key in source) {if(source.hasownProperty (key)){dist[key] = this.clone(source[key])}} return dist } } } return source } findCache(source) { for (let i = 0; i < this.cache.length; i++) { if (this.cache[i][0] === source) { return this.cache[i][1] } } } }Copy the code

Source link

Github.com/wbh13285517…