-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdeepClone.js
135 lines (125 loc) · 4.38 KB
/
deepClone.js
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* DeepClone module
* This modules creates a deep copy of a javascript object.
* JSON.parse(JSON.stringify(obj)) works only for primitive data types but
* fails for complex objects such as functions, date, regex etc.
* Although the sample data set provided in question is composed of primitive
* data but this function aims to be more generic
* This module tries to cover as many data type as possible within the time frame
* allowed.
*
* IMPORTANT: This method doesn't support cyclic objects.
*/
const CustomError = require('./CustomError');
/**
* This helper returns string representation for object type
* @param {Object} o object
* @return {String}
*/
const stringRepresentation = (o) => Object
.prototype
.toString
.call(o);
/**
* This is a helper object containing functions that
* check object against specific types
*/
const typeChecker = {
isDate: (o) => stringRepresentation(o) === '[object Date]',
isArray: (o) => stringRepresentation(o) === '[object Array]',
isRegExp: (o) => stringRepresentation(o) === '[object RegExp]'
}
/**
*
* @param {Object} obj object to checked
* @param {Object} type type against which object will be checked
* @return {Boolean} result of check
*/
const isInstanceof = (obj, type) => {
return type != null && obj instanceof type;
}
/**
* This utility returns flags attached on regular expression object
* @param {Object} re Regular expression object
*/
const getRegExpFlags = (re) => {
let flags = '';
if (re.global)
flags += 'g';
if (re.ignoreCase)
flags += 'i';
if (re.multiline)
flags += 'm';
return flags;
}
/**
* Deep clone creates a copy of source object
* such that no child reference is passed from source
* to target
* @param {Object} sourceObject object to cloned
* @return {Object} cloned object
*/
const deepClone = (sourceObject) => {
/* check if input of object */
if (typeof sourceObject !== 'object') {
/* Primitive values don't need to be cloned */
return sourceObject;
}
/* Return null if sourceType is null. This is because typeof(null) is object */
if (sourceObject === null) {
return null;
}
/* Do same for undefined */
if (sourceObject === undefined) {
return undefined;
}
let targetObject = null
if (typeChecker.isArray(sourceObject)) {
targetObject = [];
/* recursively copy all children of source object */
sourceObject.forEach(element => {
targetObject.push(deepClone(element));
});
} else if (isInstanceof(sourceObject, Promise)) {
targetObject = new Promise((resolve, reject) => {
/* Cloned is called because input to callback needs to be cloned too */
sourceObject.then((value) => {
resolve(deepClone(value));
}, (err) => {
reject(deepClone(err));
});
});
} else if (typeChecker.isRegExp(sourceObject)) {
targetObject = new RegExp(sourceObject.source, getRegExpFlags(sourceObject));
/* transfer last index if it exists
** For reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex
*/
if (sourceObject.lastIndex) {
targetObject.lastIndex = targetObject.lastIndex;
}
} else if (typeChecker.isDate(sourceObject)) {
/* This is my favorite and the easiest :D */
targetObject = new Date(sourceObject.getTime());
} else {
/** For all other types we can create target object using prototype of sourceObject
* This can work for functions, errors etc
*/
targetObject = Object.create(Object.getPrototypeOf(sourceObject))
}
/** Now we created target Object of respective types
* It's time to clone the attributes from source to target
* This operation is generic and applies to all object types
* We use getOwnPropertyNames instead of Object.keys()
* as it will return non enumerable properties too
*/
for (let prop of Object.getOwnPropertyNames(sourceObject)) {
const clonedVal = deepClone(sourceObject[prop])
const descriptor = Object.getOwnPropertyDescriptor(sourceObject, prop);
Object.defineProperty(targetObject, prop, {
...descriptor,
value: clonedVal
});
}
return targetObject;
}
module.exports = deepClone;