clone.js 7.1 KB

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