forked from zhurui/management
143 lines
3.6 KiB
JavaScript
143 lines
3.6 KiB
JavaScript
|
'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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const baseProto = Types.getInternalProto(obj);
|
||
|
let newObj;
|
||
|
|
||
|
switch (baseProto) {
|
||
|
case Types.buffer:
|
||
|
return Buffer && Buffer.from(obj); // $lab:coverage:ignore$
|
||
|
|
||
|
case Types.date:
|
||
|
return new Date(obj.getTime());
|
||
|
|
||
|
case Types.regex:
|
||
|
return new RegExp(obj);
|
||
|
|
||
|
case Types.array:
|
||
|
newObj = [];
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
if (options.prototype !== false) { // Defaults to true
|
||
|
const proto = Object.getPrototypeOf(obj);
|
||
|
if (proto &&
|
||
|
proto.isImmutable) {
|
||
|
|
||
|
return obj;
|
||
|
}
|
||
|
|
||
|
if (internals.needsProtoHack.has(baseProto)) {
|
||
|
newObj = new proto.constructor();
|
||
|
if (proto !== baseProto) {
|
||
|
Object.setPrototypeOf(newObj, proto);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
newObj = Object.create(proto);
|
||
|
}
|
||
|
}
|
||
|
else if (internals.needsProtoHack.has(baseProto)) {
|
||
|
newObj = new baseProto.constructor();
|
||
|
}
|
||
|
else {
|
||
|
newObj = {};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 (let i = 0; i < keys.length; ++i) {
|
||
|
const key = keys[i];
|
||
|
|
||
|
if (baseProto === Types.array &&
|
||
|
key === 'length') {
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||
|
if (descriptor &&
|
||
|
(descriptor.get || descriptor.set)) {
|
||
|
|
||
|
Object.defineProperty(newObj, key, descriptor);
|
||
|
}
|
||
|
else {
|
||
|
Object.defineProperty(newObj, key, {
|
||
|
enumerable: descriptor ? descriptor.enumerable : true,
|
||
|
writable: true,
|
||
|
configurable: true,
|
||
|
value: clone(obj[key], options, seen)
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (baseProto === Types.array) {
|
||
|
newObj.length = obj.length;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
};
|