clone.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. 'use strict';
  2. const Types = require('./types');
  3. const Utils = require('./utils');
  4. const internals = {
  5. needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap])
  6. };
  7. module.exports = internals.clone = function (obj, options = {}, _seen = null) {
  8. if (typeof obj !== 'object' ||
  9. obj === null) {
  10. return obj;
  11. }
  12. let clone = internals.clone;
  13. let seen = _seen;
  14. if (options.shallow) {
  15. if (options.shallow !== true) {
  16. return internals.cloneWithShallow(obj, options);
  17. }
  18. clone = (value) => value;
  19. }
  20. else {
  21. seen = seen || new Map();
  22. const lookup = seen.get(obj);
  23. if (lookup) {
  24. return lookup;
  25. }
  26. }
  27. // Built-in object types
  28. const baseProto = Types.getInternalProto(obj);
  29. if (baseProto === Types.buffer) {
  30. return Buffer && Buffer.from(obj); // $lab:coverage:ignore$
  31. }
  32. if (baseProto === Types.date) {
  33. return new Date(obj.getTime());
  34. }
  35. if (baseProto === Types.regex) {
  36. return new RegExp(obj);
  37. }
  38. // Generic objects
  39. const newObj = internals.base(obj, baseProto, options);
  40. if (newObj === obj) {
  41. return obj;
  42. }
  43. if (seen) {
  44. seen.set(obj, newObj); // Set seen, since obj could recurse
  45. }
  46. if (baseProto === Types.set) {
  47. for (const value of obj) {
  48. newObj.add(clone(value, options, seen));
  49. }
  50. }
  51. else if (baseProto === Types.map) {
  52. for (const [key, value] of obj) {
  53. newObj.set(key, clone(value, options, seen));
  54. }
  55. }
  56. const keys = Utils.keys(obj, options);
  57. for (const key of keys) {
  58. if (key === '__proto__') {
  59. continue;
  60. }
  61. if (baseProto === Types.array &&
  62. key === 'length') {
  63. newObj.length = obj.length;
  64. continue;
  65. }
  66. const descriptor = Object.getOwnPropertyDescriptor(obj, key);
  67. if (descriptor) {
  68. if (descriptor.get ||
  69. descriptor.set) {
  70. Object.defineProperty(newObj, key, descriptor);
  71. }
  72. else if (descriptor.enumerable) {
  73. newObj[key] = clone(obj[key], options, seen);
  74. }
  75. else {
  76. Object.defineProperty(newObj, key, { enumerable: false, writable: true, configurable: true, value: clone(obj[key], options, seen) });
  77. }
  78. }
  79. else {
  80. Object.defineProperty(newObj, key, {
  81. enumerable: true,
  82. writable: true,
  83. configurable: true,
  84. value: clone(obj[key], options, seen)
  85. });
  86. }
  87. }
  88. return newObj;
  89. };
  90. internals.cloneWithShallow = function (source, options) {
  91. const keys = options.shallow;
  92. options = Object.assign({}, options);
  93. options.shallow = false;
  94. const storage = Utils.store(source, keys); // Move shallow copy items to storage
  95. const copy = internals.clone(source, options); // Deep copy the rest
  96. Utils.restore(copy, source, storage); // Shallow copy the stored items and restore
  97. return copy;
  98. };
  99. internals.base = function (obj, baseProto, options) {
  100. if (baseProto === Types.array) {
  101. return [];
  102. }
  103. if (options.prototype === false) { // Defaults to true
  104. if (internals.needsProtoHack.has(baseProto)) {
  105. return new baseProto.constructor();
  106. }
  107. return {};
  108. }
  109. const proto = Object.getPrototypeOf(obj);
  110. if (proto &&
  111. proto.isImmutable) {
  112. return obj;
  113. }
  114. if (internals.needsProtoHack.has(baseProto)) {
  115. const newObj = new proto.constructor();
  116. if (proto !== baseProto) {
  117. Object.setPrototypeOf(newObj, proto);
  118. }
  119. return newObj;
  120. }
  121. return Object.create(proto);
  122. };