|
|
'use strict';
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); };
|