import { detectType } from './detector.mjs'; import { get, isCollection, set } from './collection.mjs'; import { copy } from './copier.mjs'; /** * deepcopy function * * @param {*} value * @param {Object|Function} [options] * @return {*} */ export default function deepcopy(value, options = {}) { if (typeof options === 'function') { options = { customizer: options }; } const { // TODO: before/after customizer customizer // TODO: max depth // depth = Infinity, } = options; const valueType = detectType(value); if (!isCollection(valueType)) { return recursiveCopy(value, null, null, null, customizer); } const copiedValue = copy(value, valueType, customizer); const references = new WeakMap([[value, copiedValue]]); const visited = new WeakSet([value]); return recursiveCopy(value, copiedValue, references, visited, customizer); } /** * recursively copy * * @param {*} value target value * @param {*} clone clone of value * @param {WeakMap} references visited references of clone * @param {WeakSet} visited visited references of value * @param {Function} customizer user customize function * @return {*} */ function recursiveCopy(value, clone, references, visited, customizer) { const type = detectType(value); const copiedValue = copy(value, type); // return if not a collection value if (!isCollection(type)) { return copiedValue; } let keys; switch (type) { case 'Arguments': case 'Array': keys = Object.keys(value); break; case 'Object': keys = Object.keys(value); keys.push(...Object.getOwnPropertySymbols(value)); break; case 'Map': case 'Set': keys = value.keys(); break; default: } // walk within collection with iterator for (let collectionKey of keys) { const collectionValue = get(value, collectionKey, type); if (visited.has(collectionValue)) { // for [Circular] set(clone, collectionKey, references.get(collectionValue), type); } else { const collectionValueType = detectType(collectionValue); const copiedCollectionValue = copy(collectionValue, collectionValueType); // save reference if value is collection if (isCollection(collectionValueType)) { references.set(collectionValue, copiedCollectionValue); visited.add(collectionValue); } set( clone, collectionKey, recursiveCopy( collectionValue, copiedCollectionValue, references, visited, customizer ), type ); } } // TODO: isSealed/isFrozen/isExtensible return clone; }