Deep Clone

Shallow Clone VS Deep Clone

  • Shallow Clone
    It is copying the reference pointer of the object which means the new object is also pointing towards the same memory reference of the old object.

  • Deep Clone
    The object will produce a copy of itself, a new memory is allocated for the object and its contents.

Shallow Clone

Traversing object properties

1
2
3
4
5
6
7
8
9
function shallowClone(source) {
let target = {};
for(let i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
1
2
3
let a1 = {b: {c: {}};
let a2 = shallowClone(a1);
a2.b.c === a1.b.c // true

Object.assign()

1
2
3
4
5
let obj1 = { a: 10, b: 20, c: 30 };
let obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1); // { a: 10, b: 20, c: 30 } <-- not changed
console.log(obj2); // { a: 10, b: 100, c: 30 }
1
2
3
4
let obj = { a: {a: "hello", b: 21} };
let cloneObj = Object.assign({}, obj);
cloneObj.a.a = "changed";
console.log(obj.a.a); // "changed"

Deep Clone

JSON.parse

1
2
3
4
5
6
7
8
9
10
11
const oldObj = {
a: 1,
b: [ 'e', 'f', 'g' ],
c: { h: { i: 2 } }
};

const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // false
newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 2 }

This is easy, but it does not suit some situations.

  • Can not clone function or Array object
  • Lose the constructor of Object
  • Throw error when Objects have circular references
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// constructor
function person(pname) {
this.name = pname;
}

const Fanjia = new person('Fanjia');

// function
function say() {
console.log('hi');
};

const oldObj = {
a: say,
b: new Array(1),
c: Fanjia
};

const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.a, oldObj.a); // undefined [Function: say]
console.log(newObj.b[0], oldObj.b[0]); // null undefined
console.log(newObj.c.constructor, oldObj.c.constructor); // [Function: Object] [Function: person]

const Circular = {};
Circular.a = Circular;
const newCircular = JSON.parse(JSON.stringify(Circular));
console.log(newCircular.a, Circular.a); // TypeError: Converting circular structure to JSON

Recursion

Check the object type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function isType(obj, type) {
if (typeof obj !== 'object') {
return false;
}
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case 'Array':
flag = typeString === '[object Array]';
break;
case 'Date':
flag = typeString === '[object Date]';
break;
default:
flag = false;
}
return flag;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function deepClone(source) {
// store the circular references
const oldObjArr = [];
const newObjArr = [];

const _clone = oldObj => {
if (oldObj === null) {
return null;
}
if (typeof oldObj !== 'object') {
return oldObj;
}

let newObj,proto;

if (isType(oldObj, 'Array')) {
newObj = [];
} else if (isType(oldObj, 'Date')) {
newObj = new Date(oldObj.getTime());
} else {
proto = Object.getPrototypeOf(oldObj);
// use Object.create cut the prototype chain
newObj = Object.create(proto);
}

// circular reference index
const index = oldObjArr.indexOf(oldObj);
if (index != -1) {
return newObjArr[index];
}
oldObjArr.push(oldObj);
newObjArr.push(newObj);

for (let i in oldObj) {
newObj[i] = _clone(oldObj[i]);
}

return newObj;
};

return _clone(source);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// constructor
function person(pname) {
this.name = pname;
}

const Fanjia = new person('Fanjia');

// function
function say() {
console.log('hi');
};

const oldObj = {
a: say,
b: new Array(1),
c: Fanjia
};

const newObj = deepClone(oldObj);
console.log(newObj.a, oldObj.a); //[Function: say] [Function: say]
console.log(newObj.b[0], oldObj.b[0]); // undefined undefined
console.log(newObj.c.constructor, oldObj.c.constructor); // [Function: person][Function: person]

But there is still a question: stack overflow.

Loop

We can use loop instead of recursion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function cloneLoop(source) {
const uniqueList = [];
const root = {};
// stack
const loopList = [
{
parent: root,
key: undefined,
data: source,
}
];

while(loopList.length) {
// Depth First Search
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;

let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}

let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
continue;
}
uniqueList.push({
source: data,
target: res,
});

for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}

return root;
}
function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}

Reference

https://juejin.im/post/5bc1ae9be51d450e8b140b0c

https://juejin.im/post/5abb55ee6fb9a028e33b7e0a

1
2