MEMEPh. ideas that are worth sharing...

Shallow and deep copies

Foreword


There are different ways to copy objects in javascript, if you are not familiar with the language, it is easy to fall into the trap when copying objects, so how can we copy an object properly?

After reading this article, I hope you can understand:

 

Shallow copy and deep copy


var a1 = {b: {c: {}};
var a2 = shallowClone(a1); // shallow copy method
a2.b.c === a1.b.c // true old and new objects still share the same memory


var a3 = deepClone(a3); // deep copy method
a3.b.c === a1.b.c // false The new object does not share memory with the original object

With the help of the following two pictures of ConardLi , help us better understand the meaning of the two:

All in all, a shallow copy only copies a pointer to an object, not the object itself, and the old and new objects still share the same piece of memory . But deep copying will create another identical object, the new object does not share memory with the original object , and modifying the new object will not change the original object.

 

Difference between assignment and deep/shallow copy


The difference between the three is as follows, but the premise of the comparison is for reference types :

Let's first look at the following example, comparing the effect of assignment and deep/shallow copy on the original object after modification:

// object assignment

let obj1 = {
name : 'boating in the waves',
arr : [1,[2,3],4],
};

let obj2 = obj1;
obj2.name = "Alang";
obj2.arr[1] =[5,6,7];
console.log('obj1',obj1) // obj1 { name: 'Alang', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: 'Alang', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// shallow copy
let obj1 = {
name : 'boating in the waves',
arr : [1,[2,3],4],
};

let obj3=shallowClone(obj1)
obj3.name = "Alang";
obj3.arr[1] = [5,6,7] ; // The old and new objects still share the same memory

// This is a shallow copy method

function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
} 
} 

return target;
}

console.log('obj1',obj1) // obj1 { name: 'boat in waves', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'Alang', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// deep copy

let obj1 = {
name : 'boating in the waves',
arr : [1,[2,3],4],
};

let obj4=deepClone(obj1)
obj4.name = "Alang";
obj4.arr[1] = [5,6,7] ; // The new object does not share memory with the original object

// This is a deep copy method

function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {

// implement a recursive copy

cloneObj[key] = deepClone(obj[key]);
} 

} 

return cloneObj;

}

console.log('obj1',obj1) // obj1 { name: 'boat in the waves', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'Alang', arr: [ 1, [ 5, 6, 7 ], 4 ] }

In the above example, obj1 is the original object, obj2 is the object obtained by the assignment operation, the object obtained by the shallow copy of obj3, and the object obtained by the deep copy of obj4, through the following table, we can clearly see their impact on the original data:



 

Shallow copy implementation


1.Object.assign()

The Object.assign() method can copy any number of enumerable properties of the source object itself to the target object, and then return the target object.

let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }

 

2. The _.clone method of the function library lodash

The library also provides _.clone for Shallow Copy, and we will introduce the use of this library to implement deep copy later.

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true

 

3. Spread operator...

The spread operator is an es6/es2015 feature that provides a very convenient way to perform a shallow copy, which does the same thing as Object.assign().

let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }

 

4. Array.prototype.concat()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]

 

5. Array.prototype.slice()

let arr = [1, 3, {
    username: ' kobe'
    }];

let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]

 

Implementation of deep copy


1. JSON.parse(JSON.stringify())

let arr = [1, 3, {
    username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)

 

This is also to use JSON.stringify to convert the object into a JSON string, and then use JSON.parse to parse the string into an object. One by one, a new object is generated, and the object will open up a new stack to achieve deep copying.

Although this method can realize deep copying of arrays or objects, it cannot handle functions and regularities , because after these two are processed based on JSON.stringify and JSON.parse, the obtained regularity is no longer regular (it becomes an empty object), and the result is is no longer a function (becomes null).

 

For example the following example:

let arr = [1, 3, {
    username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)


 

2. The _.cloneDeep method of the function library lodash

The library also provides _.cloneDeep for Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

 

3. jQuery.extend() method

jQuery provides a $.extendtool that can be used to do Deep Copy

$.extend(deepCopy, target, object1, [objectN])//The first parameter is true, which is a deep copy
var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

 

4. Handwritten recursive method

The recursive method implements the principle of deep cloning: traverse objects and arrays until they are all basic data types, and then copy them, which is a deep copy .

There is a special case to note that the object has a circular reference , that is, the property of the object directly refers to itself, to solve the circular reference problem, we can open up an additional storage space to store the current object and the corresponding relationship between the copied object, When you need to copy the current object, first go to the storage space to find out if the object has been copied, if so, return it directly, if not, continue to copy, so as to solve the problem of circular reference ingeniously. 

function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // if it is null or undefined I will not perform the copy operation
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);

// May be an object or a normal value If it is a function, no deep copy is required
if (typeof obj !== "object") return obj;

// If it is an object, a deep copy is required
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();

// The constructor on the prototype of the class to which it belongs is found, and the constructor on the prototype points to the current class itself
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {

// implement a recursive copy
cloneObj[key] = deepClone(obj[key], hash);
} 

} 
return cloneObj;

}

let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // the object has a circular reference
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);