Spaces:
Sleeping
Sleeping
; | |
const Types = require('./types'); | |
const Utils = require('./utils'); | |
const internals = { | |
needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap]) | |
}; | |
module.exports = internals.clone = function (obj, options = {}, _seen = null) { | |
if (typeof obj !== 'object' || | |
obj === null) { | |
return obj; | |
} | |
let clone = internals.clone; | |
let seen = _seen; | |
if (options.shallow) { | |
if (options.shallow !== true) { | |
return internals.cloneWithShallow(obj, options); | |
} | |
clone = (value) => value; | |
} | |
else { | |
seen = seen || new Map(); | |
const lookup = seen.get(obj); | |
if (lookup) { | |
return lookup; | |
} | |
} | |
// Built-in object types | |
const baseProto = Types.getInternalProto(obj); | |
if (baseProto === Types.buffer) { | |
return Buffer && Buffer.from(obj); // $lab:coverage:ignore$ | |
} | |
if (baseProto === Types.date) { | |
return new Date(obj.getTime()); | |
} | |
if (baseProto === Types.regex) { | |
return new RegExp(obj); | |
} | |
// Generic objects | |
const newObj = internals.base(obj, baseProto, options); | |
if (newObj === obj) { | |
return obj; | |
} | |
if (seen) { | |
seen.set(obj, newObj); // Set seen, since obj could recurse | |
} | |
if (baseProto === Types.set) { | |
for (const value of obj) { | |
newObj.add(clone(value, options, seen)); | |
} | |
} | |
else if (baseProto === Types.map) { | |
for (const [key, value] of obj) { | |
newObj.set(key, clone(value, options, seen)); | |
} | |
} | |
const keys = Utils.keys(obj, options); | |
for (const key of keys) { | |
if (key === '__proto__') { | |
continue; | |
} | |
if (baseProto === Types.array && | |
key === 'length') { | |
newObj.length = obj.length; | |
continue; | |
} | |
const descriptor = Object.getOwnPropertyDescriptor(obj, key); | |
if (descriptor) { | |
if (descriptor.get || | |
descriptor.set) { | |
Object.defineProperty(newObj, key, descriptor); | |
} | |
else if (descriptor.enumerable) { | |
newObj[key] = clone(obj[key], options, seen); | |
} | |
else { | |
Object.defineProperty(newObj, key, { enumerable: false, writable: true, configurable: true, value: clone(obj[key], options, seen) }); | |
} | |
} | |
else { | |
Object.defineProperty(newObj, key, { | |
enumerable: true, | |
writable: true, | |
configurable: true, | |
value: clone(obj[key], options, seen) | |
}); | |
} | |
} | |
return newObj; | |
}; | |
internals.cloneWithShallow = function (source, options) { | |
const keys = options.shallow; | |
options = Object.assign({}, options); | |
options.shallow = false; | |
const storage = Utils.store(source, keys); // Move shallow copy items to storage | |
const copy = internals.clone(source, options); // Deep copy the rest | |
Utils.restore(copy, source, storage); // Shallow copy the stored items and restore | |
return copy; | |
}; | |
internals.base = function (obj, baseProto, options) { | |
if (baseProto === Types.array) { | |
return []; | |
} | |
if (options.prototype === false) { // Defaults to true | |
if (internals.needsProtoHack.has(baseProto)) { | |
return new baseProto.constructor(); | |
} | |
return {}; | |
} | |
const proto = Object.getPrototypeOf(obj); | |
if (proto && | |
proto.isImmutable) { | |
return obj; | |
} | |
if (internals.needsProtoHack.has(baseProto)) { | |
const newObj = new proto.constructor(); | |
if (proto !== baseProto) { | |
Object.setPrototypeOf(newObj, proto); | |
} | |
return newObj; | |
} | |
return Object.create(proto); | |
}; | |