From f6eb28d79f8a0f158db3b59f771f0e4f00ec7dd2 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 17 Oct 2019 13:53:04 -0700 Subject: [PATCH] Improve clone() performance. Closes #344 --- lib/clone.js | 119 +++++++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/lib/clone.js b/lib/clone.js index 0fbe8359..c3968cfa 100755 --- a/lib/clone.js +++ b/lib/clone.js @@ -36,48 +36,26 @@ module.exports = internals.clone = function (obj, options = {}, _seen = null) { } } + // Built-in object types + 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 (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) { @@ -96,24 +74,31 @@ module.exports = internals.clone = function (obj, options = {}, _seen = null) { } const keys = Utils.keys(obj, options); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - + for (const key of keys) { if (baseProto === Types.array && key === 'length') { + newObj.length = obj.length; continue; } const descriptor = Object.getOwnPropertyDescriptor(obj, key); - if (descriptor && - (descriptor.get || descriptor.set)) { + if (descriptor) { + if (descriptor.get || + descriptor.set) { - Object.defineProperty(newObj, key, descriptor); + 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: descriptor ? descriptor.enumerable : true, + enumerable: true, writable: true, configurable: true, value: clone(obj[key], options, seen) @@ -121,10 +106,6 @@ module.exports = internals.clone = function (obj, options = {}, _seen = null) { } } - if (baseProto === Types.array) { - newObj.length = obj.length; - } - return newObj; }; @@ -140,3 +121,37 @@ internals.cloneWithShallow = function (source, options) { 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); +};