UNPKG

7.29 kBJavaScriptView Raw
1var clone = (function() {
2'use strict';
3
4function _instanceof(obj, type) {
5 return type != null && obj instanceof type;
6}
7
8var nativeMap;
9try {
10 nativeMap = Map;
11} catch(_) {
12 // maybe a reference error because no `Map`. Give it a dummy value that no
13 // value will ever be an instanceof.
14 nativeMap = function() {};
15}
16
17var nativeSet;
18try {
19 nativeSet = Set;
20} catch(_) {
21 nativeSet = function() {};
22}
23
24var nativePromise;
25try {
26 nativePromise = Promise;
27} catch(_) {
28 nativePromise = function() {};
29}
30
31/**
32 * Clones (copies) an Object using deep copying.
33 *
34 * This function supports circular references by default, but if you are certain
35 * there are no circular references in your object, you can save some CPU time
36 * by calling clone(obj, false).
37 *
38 * Caution: if `circular` is false and `parent` contains circular references,
39 * your program may enter an infinite loop and crash.
40 *
41 * @param `parent` - the object to be cloned
42 * @param `circular` - set to true if the object to be cloned may contain
43 * circular references. (optional - true by default)
44 * @param `depth` - set to a number if the object is only to be cloned to
45 * a particular depth. (optional - defaults to Infinity)
46 * @param `prototype` - sets the prototype to be used when cloning an object.
47 * (optional - defaults to parent prototype).
48 * @param `includeNonEnumerable` - set to true if the non-enumerable properties
49 * should be cloned as well. Non-enumerable properties on the prototype
50 * chain will be ignored. (optional - false by default)
51*/
52function clone(parent, circular, depth, prototype, includeNonEnumerable) {
53 if (typeof circular === 'object') {
54 depth = circular.depth;
55 prototype = circular.prototype;
56 includeNonEnumerable = circular.includeNonEnumerable;
57 circular = circular.circular;
58 }
59 // maintain two arrays for circular references, where corresponding parents
60 // and children have the same index
61 var allParents = [];
62 var allChildren = [];
63
64 var useBuffer = typeof Buffer != 'undefined';
65
66 if (typeof circular == 'undefined')
67 circular = true;
68
69 if (typeof depth == 'undefined')
70 depth = Infinity;
71
72 // recurse this function so we don't reset allParents and allChildren
73 function _clone(parent, depth) {
74 // cloning null always returns null
75 if (parent === null)
76 return null;
77
78 if (depth === 0)
79 return parent;
80
81 var child;
82 var proto;
83 if (typeof parent != 'object') {
84 return parent;
85 }
86
87 if (_instanceof(parent, nativeMap)) {
88 child = new nativeMap();
89 } else if (_instanceof(parent, nativeSet)) {
90 child = new nativeSet();
91 } else if (_instanceof(parent, nativePromise)) {
92 child = new nativePromise(function (resolve, reject) {
93 parent.then(function(value) {
94 resolve(_clone(value, depth - 1));
95 }, function(err) {
96 reject(_clone(err, depth - 1));
97 });
98 });
99 } else if (clone.__isArray(parent)) {
100 child = [];
101 } else if (clone.__isRegExp(parent)) {
102 child = new RegExp(parent.source, __getRegExpFlags(parent));
103 if (parent.lastIndex) child.lastIndex = parent.lastIndex;
104 } else if (clone.__isDate(parent)) {
105 child = new Date(parent.getTime());
106 } else if (useBuffer && Buffer.isBuffer(parent)) {
107 if (Buffer.allocUnsafe) {
108 // Node.js >= 4.5.0
109 child = Buffer.allocUnsafe(parent.length);
110 } else {
111 // Older Node.js versions
112 child = new Buffer(parent.length);
113 }
114 parent.copy(child);
115 return child;
116 } else if (_instanceof(parent, Error)) {
117 child = Object.create(parent);
118 } else {
119 if (typeof prototype == 'undefined') {
120 proto = Object.getPrototypeOf(parent);
121 child = Object.create(proto);
122 }
123 else {
124 child = Object.create(prototype);
125 proto = prototype;
126 }
127 }
128
129 if (circular) {
130 var index = allParents.indexOf(parent);
131
132 if (index != -1) {
133 return allChildren[index];
134 }
135 allParents.push(parent);
136 allChildren.push(child);
137 }
138
139 if (_instanceof(parent, nativeMap)) {
140 parent.forEach(function(value, key) {
141 var keyChild = _clone(key, depth - 1);
142 var valueChild = _clone(value, depth - 1);
143 child.set(keyChild, valueChild);
144 });
145 }
146 if (_instanceof(parent, nativeSet)) {
147 parent.forEach(function(value) {
148 var entryChild = _clone(value, depth - 1);
149 child.add(entryChild);
150 });
151 }
152
153 for (var i in parent) {
154 var attrs;
155 if (proto) {
156 attrs = Object.getOwnPropertyDescriptor(proto, i);
157 }
158
159 if (attrs && attrs.set == null) {
160 continue;
161 }
162 child[i] = _clone(parent[i], depth - 1);
163 }
164
165 if (Object.getOwnPropertySymbols) {
166 var symbols = Object.getOwnPropertySymbols(parent);
167 for (var i = 0; i < symbols.length; i++) {
168 // Don't need to worry about cloning a symbol because it is a primitive,
169 // like a number or string.
170 var symbol = symbols[i];
171 var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
172 if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
173 continue;
174 }
175 child[symbol] = _clone(parent[symbol], depth - 1);
176 if (!descriptor.enumerable) {
177 Object.defineProperty(child, symbol, {
178 enumerable: false
179 });
180 }
181 }
182 }
183
184 if (includeNonEnumerable) {
185 var allPropertyNames = Object.getOwnPropertyNames(parent);
186 for (var i = 0; i < allPropertyNames.length; i++) {
187 var propertyName = allPropertyNames[i];
188 var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
189 if (descriptor && descriptor.enumerable) {
190 continue;
191 }
192 child[propertyName] = _clone(parent[propertyName], depth - 1);
193 Object.defineProperty(child, propertyName, {
194 enumerable: false
195 });
196 }
197 }
198
199 return child;
200 }
201
202 return _clone(parent, depth);
203}
204
205/**
206 * Simple flat clone using prototype, accepts only objects, usefull for property
207 * override on FLAT configuration object (no nested props).
208 *
209 * USE WITH CAUTION! This may not behave as you wish if you do not know how this
210 * works.
211 */
212clone.clonePrototype = function clonePrototype(parent) {
213 if (parent === null)
214 return null;
215
216 var c = function () {};
217 c.prototype = parent;
218 return new c();
219};
220
221// private utility functions
222
223function __objToStr(o) {
224 return Object.prototype.toString.call(o);
225}
226clone.__objToStr = __objToStr;
227
228function __isDate(o) {
229 return typeof o === 'object' && __objToStr(o) === '[object Date]';
230}
231clone.__isDate = __isDate;
232
233function __isArray(o) {
234 return typeof o === 'object' && __objToStr(o) === '[object Array]';
235}
236clone.__isArray = __isArray;
237
238function __isRegExp(o) {
239 return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
240}
241clone.__isRegExp = __isRegExp;
242
243function __getRegExpFlags(re) {
244 var flags = '';
245 if (re.global) flags += 'g';
246 if (re.ignoreCase) flags += 'i';
247 if (re.multiline) flags += 'm';
248 return flags;
249}
250clone.__getRegExpFlags = __getRegExpFlags;
251
252return clone;
253})();
254
255if (typeof module === 'object' && module.exports) {
256 module.exports = clone;
257}