LThere are different ways to copy an object in javascript, and it’s easy to fall into the trap of copying an object if you’re not familiar with the language yet, so how do we copy an object correctly?
After reading this article, I hope you understand:
- What are deep/shallow copies and how do they differ from assignments?
- How many ways are deep/shallow copies implemented?
Shallow vs. deep copy
A shallow copy is the creation of a new object that has an exact copy of the values of the properties of the original object. If the attribute is a basic type, the copy is the value of the basic type, and if the attribute is a reference type, the copy is the memory address, so if one of the objects changes this address, it affects the other object.
Deep copy is a complete copy of an object from memory, from the heap memory to create a new area to store the new object, and modify the new object will not affect the original object.
var a1 = {b: {c: {}};
var a2 = shallowClone(a1);
a2.b.c === a1.b.c
var a3 = deepClone(a3);
a3.b.c === a1.b.c
In a nutshell, a shallow copy only copies the pointer to an object, not the object itself, and the old and new objects still share the same memory. However, a deep copy creates an identical object, and the new object does not share memory with the original object, so modifying the new object will not change the original object.
Difference between assignment and deep/shallow copying
The differences between the three are as follows, though the premise of the comparison is all for reference types:
When we assign an object to a new variable, we are actually assigning the object’s address on the stack, not the data on the heap. That is, the two objects point to the same storage space, no matter which object is changed, in fact, the content of the storage space is changed, therefore, the two objects are linked.
Shallow copy: re-create the memory in the heap, the basic data types of the objects before and after the copy do not affect each other, but the reference types of the objects before and after the copy will affect each other because they share the same block of memory.
Deep copy: open a new area in heap memory to store the new object, recursive copy of the object’s children, before and after the copy of the two objects do not affect each other.
Let’s look at the following example to compare the effect on the original object after modification of the object obtained by assignment versus deep/shallow copy:
let obj1 = {
name : 'al',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "anan";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1) // obj1 { name: 'anan', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: 'anan', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 浅拷贝
let obj1 = {
name : 'al',
arr : [1,[2,3],4],
};
let obj3=shallowClone(obj1)
obj3.name = "anan";
obj3.arr[1] = [5,6,7] ;
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: 'al', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'an', arr: [ 1, [ 5, 6, 7 ], 4 ] }
let obj1 = {
name : 'al',
arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "al";
obj4.arr[1] = [5,6,7] ;
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)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
console.log('obj1',obj1)
console.log('obj4',obj4)
In the above example, obj1 is the original object, obj2 is the object obtained from the assignment operation, obj3 is the object obtained from the shallow copy, obj4 is the object obtained from the deep copy, through the following table, we can clearly see their impact on the original data:
Shallow copy implementation
1.Object.assign()
The Object.assign() method copies any number of enumerable properties of the source object itself to the target object and returns 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. library lodash _.clone method
The library also provides _.clone used to do Shallow Copy, later we will introduce the use of this library to achieve deep copy.
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. Expanding operators…
The expand operator is an es6 / es2015 feature that provides a very convenient way to perform shallow copies, which is the same function 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' } ]
Deep copy implementation
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 the use of JSON.stringify the object into a JSON string, and then use JSON.parse to parse the string into an object, one to go, the new object is generated, and the object will open up a new stack, to achieve deep copy.
This method can be realized although the array or object deep copy, but can not handle the function and regular, because the two based on JSON.stringify and JSON.parse processing, the regular is no longer regular (into the empty object), get the function is no longer a function (into the 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. library lodash _.cloneDeep method
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 $.extend
which can be used to do Deep Copy.
$.extend(deepCopy, target, object1, [objectN])
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 methods
Recursive method to realize the principle of deep cloning: traversing the object, array until inside all the basic data types, and then go to copy, is a deep copy.
There is a special case need to pay attention to is the existence of circular references to the object, that is, the object’s attributes directly refer to its own situation, to solve the problem of circular references, we can open up an additional storage space to store the current object and the copy of the object’s correspondence, when you need to copy the current object, first go to the storage space to find out whether there is a copy of the object, if there is a direct return, if there is not then If you don’t have it, you can continue to copy it. This is a clever way to solve the problem of circular references. On this piece if you have any doubts, please carefully read ConardLi大佬
how to write a stunning interviewer deep copy? This article is for more information on how to write a deep copy that will wow your interviewer.
function deepClone(obj, hash = new WeakMap()) {
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;
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj;
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);