index.mjs 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { detectType } from './detector.mjs';
  2. import { get, isCollection, set } from './collection.mjs';
  3. import { copy } from './copier.mjs';
  4. /**
  5. * deepcopy function
  6. *
  7. * @param {*} value
  8. * @param {Object|Function} [options]
  9. * @return {*}
  10. */
  11. export default function deepcopy(value, options = {}) {
  12. if (typeof options === 'function') {
  13. options = {
  14. customizer: options
  15. };
  16. }
  17. const {
  18. // TODO: before/after customizer
  19. customizer
  20. // TODO: max depth
  21. // depth = Infinity,
  22. } = options;
  23. const valueType = detectType(value);
  24. if (!isCollection(valueType)) {
  25. return recursiveCopy(value, null, null, null, customizer);
  26. }
  27. const copiedValue = copy(value, valueType, customizer);
  28. const references = new WeakMap([[value, copiedValue]]);
  29. const visited = new WeakSet([value]);
  30. return recursiveCopy(value, copiedValue, references, visited, customizer);
  31. }
  32. /**
  33. * recursively copy
  34. *
  35. * @param {*} value target value
  36. * @param {*} clone clone of value
  37. * @param {WeakMap} references visited references of clone
  38. * @param {WeakSet} visited visited references of value
  39. * @param {Function} customizer user customize function
  40. * @return {*}
  41. */
  42. function recursiveCopy(value, clone, references, visited, customizer) {
  43. const type = detectType(value);
  44. const copiedValue = copy(value, type);
  45. // return if not a collection value
  46. if (!isCollection(type)) {
  47. return copiedValue;
  48. }
  49. let keys;
  50. switch (type) {
  51. case 'Arguments':
  52. case 'Array':
  53. keys = Object.keys(value);
  54. break;
  55. case 'Object':
  56. keys = Object.keys(value);
  57. keys.push(...Object.getOwnPropertySymbols(value));
  58. break;
  59. case 'Map':
  60. case 'Set':
  61. keys = value.keys();
  62. break;
  63. default:
  64. }
  65. // walk within collection with iterator
  66. for (let collectionKey of keys) {
  67. const collectionValue = get(value, collectionKey, type);
  68. if (visited.has(collectionValue)) {
  69. // for [Circular]
  70. set(clone, collectionKey, references.get(collectionValue), type);
  71. } else {
  72. const collectionValueType = detectType(collectionValue);
  73. const copiedCollectionValue = copy(collectionValue, collectionValueType);
  74. // save reference if value is collection
  75. if (isCollection(collectionValueType)) {
  76. references.set(collectionValue, copiedCollectionValue);
  77. visited.add(collectionValue);
  78. }
  79. set(
  80. clone,
  81. collectionKey,
  82. recursiveCopy(
  83. collectionValue,
  84. copiedCollectionValue,
  85. references,
  86. visited,
  87. customizer
  88. ),
  89. type
  90. );
  91. }
  92. }
  93. // TODO: isSealed/isFrozen/isExtensible
  94. return clone;
  95. }