You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
4.1 KiB
176 lines
4.1 KiB
'use strict'; |
|
|
|
const Reach = require('./reach'); |
|
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 if (seen) { |
|
const lookup = seen.get(obj); |
|
if (lookup) { |
|
return lookup; |
|
} |
|
} |
|
else { |
|
seen = new Map(); |
|
} |
|
|
|
// 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 seen = new Map(); |
|
|
|
for (const key of keys) { |
|
const ref = Reach(source, key); |
|
if (typeof ref === 'object' || |
|
typeof ref === 'function') { |
|
|
|
seen.set(ref, ref); |
|
} |
|
} |
|
|
|
return internals.clone(source, options, seen); |
|
}; |
|
|
|
|
|
internals.base = function (obj, baseProto, options) { |
|
|
|
if (options.prototype === false) { // Defaults to true |
|
if (internals.needsProtoHack.has(baseProto)) { |
|
return new baseProto.constructor(); |
|
} |
|
|
|
return baseProto === Types.array ? [] : {}; |
|
} |
|
|
|
const proto = Object.getPrototypeOf(obj); |
|
if (proto && |
|
proto.isImmutable) { |
|
|
|
return obj; |
|
} |
|
|
|
if (baseProto === Types.array) { |
|
const newObj = []; |
|
if (proto !== baseProto) { |
|
Object.setPrototypeOf(newObj, proto); |
|
} |
|
|
|
return newObj; |
|
} |
|
|
|
if (internals.needsProtoHack.has(baseProto)) { |
|
const newObj = new proto.constructor(); |
|
if (proto !== baseProto) { |
|
Object.setPrototypeOf(newObj, proto); |
|
} |
|
|
|
return newObj; |
|
} |
|
|
|
return Object.create(proto); |
|
};
|
|
|