177 lines
4.1 KiB
JavaScript
Executable file
177 lines
4.1 KiB
JavaScript
Executable file
'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);
|
|
};
|