Toggle navigation
MeasureThat.net
Create a benchmark
Tools
Feedback
FAQ
Register
Log In
_.cloneDeep vs structuredClone vs cloneDeep
(version: 0)
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
Comparing performance of:
Lodash cloneDeep vs Native structuredClone vs mt cloneDeep
Created:
2 years ago
by:
Guest
Jump to the latest result
HTML Preparation code:
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'></script>
Script Preparation code:
const Tag = Object.freeze({ ARGUMENTS: '[object Arguments]', ARRAY: '[object Array]', BOOLEAN: '[object Boolean]', DATE: '[object Date]', ERROR: '[object Error]', MAP: '[object Map]', NUMBER: '[object Number]', OBJECT: '[object Object]', REGEXP: '[object RegExp]', SET: '[object Set]', STRING: '[object String]', SYMBOL: '[object Symbol]', WEAKMAP: '[object WeakMap]', WEAKSET: "[object WeakSet]", ARRAYBUFFER: '[object ArrayBuffer]', DATAVIEW: '[object DataView]', FLOAT32: '[object Float32Array]', FLOAT64: '[object Float64Array]', INT8: '[object Int8Array]', INT16: '[object Int16Array]', INT32: '[object Int32Array]', UINT8: '[object Uint8Array]', UINT8CLAMPED: '[object Uint8ClampedArray]', UINT16: '[object Uint16Array]', UINT32: '[object Uint32Array]', BIGINT64: "[object BigInt64Array]", BIGUINT64: "[object BigUint64Array]" }); function cloneInternalNoRecursion(_value, customizer, log, doThrow) { if (typeof log !== "function") log = console.warn; let result; // Will be used to store cloned values so that we don't loop infinitely on // circular references. const cloneStore = new Map(); // This symbol is used to indicate that the cloned value is the top-level // object that will be returned by the function. const TOP_LEVEL = Symbol("TOP_LEVEL"); // A queue so we can avoid recursion. const queue = [{ value: _value, parentOrAssigner: TOP_LEVEL }]; // We will do a second pass through everything to check Object.isExtensible, // Object.isSealed and Object.isFrozen. We do it last so we don't run into // issues where we append properties on a frozen object, etc const isExtensibleSealFrozen = []; function warn(message, cause) { class CloneDeepWarning extends Error { constructor(message, cause) { super(message, cause); this.name = CloneDeepWarning.name; } } return new CloneDeepWarning(message, cause); } function assign(cloned, parentOrAssigner, prop, metadata) { if (parentOrAssigner === TOP_LEVEL) result = cloned; else if (typeof parentOrAssigner === "function") parentOrAssigner(cloned, prop, metadata); else if (typeof metadata === "object") { const hasAccessor = ["get", "set"].some(key => typeof metadata[key] === "function"); // `cloned` or getAccessor will determine the value delete metadata.value; // defineProperty throws if property with accessors is writeable if (hasAccessor) { delete metadata.writable; log(warn("Cloning value whose property descriptor is a get " + "or set accessor.")); } Object.defineProperty(parentOrAssigner, prop, Object.assign( // defineProperty throws if value and set/get accessor coexist hasAccessor ? {} : { value: cloned }, metadata, )); } else parentOrAssigner[prop] = cloned; return cloned; } function tagOf(value) { return Object.prototype.toString.call(value); } for (let obj = queue.shift(); obj !== undefined; obj = queue.shift()) { // `value` is the value to deeply clone // `parentOrAssigner` is either // - TOP_LEVEL - this value is the top-level object that will be // returned by the function // - object - a parent object this value is nested under // - function - an "assigner" that has the responsiblity of // assigning the cloned value to something // `prop` is used with `parentOrAssigner` if it is an object so that the // cloned object will be assigned to `parentOrAssigner[prop]`. // `metadata` contains the property descriptor(s) for the value. It may // be undefined. const { value, parentOrAssigner, prop, metadata } = obj; // Will contain the cloned object. let cloned; // Check for circular references. const seen = cloneStore.get(value); if (seen !== undefined) { assign(seen, parentOrAssigner, prop, metadata); continue; } // If true, do not not clone the properties of value. let ignoreProps; // If true, do not have `cloned` share the prototype of `value`. let ignoreProto; // Is true if the customizer determines the value of `cloned`. let useCustomizerClone; // Perform user-injected logic if applicable. if (typeof customizer === "function") { let clone, additionalValues, ignore; try { const customResult = customizer(value); if (typeof customResult === "object") { useCustomizerClone = true; // Must wrap destructure in () if not variable declaration ({ clone, additionalValues, ignore, ignoreProps, ignoreProto } = customResult); if (ignore === true) continue; cloned = assign(clone, parentOrAssigner, prop, metadata); if (Array.isArray(additionalValues)) additionalValues.forEach(object => { if (typeof object === "object") { queue.push({ value: object.value, parentOrAssigner: object.assigner }); } }); } } catch(error) { if (doThrow === true) throw error; clone = undefined; useCustomizerClone = false; error.message = "customizer encountered error. Its results " + "will be ignored for the current value, and " + "the algorithm will proceed with default " + "behavior. Error encountered: " + error.message; log(warn(error.message, error.cause)); } } try { // skip the following "else if" branches if (useCustomizerClone === true) {} // If value is primitive, just assign it directly. else if (value === null || !["object", "function"] .includes(typeof value)) { assign(value, parentOrAssigner, prop, metadata); continue; } // We won't clone weakmaps or weaksets. else if ([Tag.WEAKMAP, Tag.WEAKSET].includes(tagOf(value))) throw warn(`Attempted to clone unsupported type${ typeof value.constructor === "function" && typeof value.constructor.name === "string" ? ` ${value.constructor.name}` : "" }.`); // We only copy functions if they are methods. else if (typeof value === "function") { cloned = assign(parentOrAssigner !== TOP_LEVEL ? value : {}, parentOrAssigner, prop, metadata); log(warn(`Attempted to clone function${typeof prop === "string" ? ` with name ${prop}` : "" }. ` + "JavaScript functions cannot be cloned. If this " + "function is a method, then it will be copied "+ "directly.")); if (parentOrAssigner === TOP_LEVEL) continue; } // If value is a Node Buffer, just use Buffer's subarray method. else if (typeof global === "object" && global.Buffer && typeof Buffer === "function" && typeof Buffer.isBuffer === "function" && Buffer.isBuffer(value)) cloned = assign(value.subarray(), parentOrAssigner, prop, metadata); else if (Array.isArray(value)) cloned = assign(new Array(value.length), parentOrAssigner, prop, metadata); // Ordinary objects, or the rare `arguments` clone else if ([Tag.OBJECT, Tag.ARGUMENTS].includes(tagOf(value))) cloned = assign(Object.create(Object.getPrototypeOf(value)), parentOrAssigner, prop, metadata); // values that will be called using contructor else { const Value = value.constructor; // Booleans, Number, String or Symbols which used `new` syntax // so JavaScript thinks they are objects // We also handle Date here because it is convenient if ([Tag.BOOLEAN, Tag.DATE].includes(tagOf(value))) cloned = assign(new Value(Number(value)), parentOrAssigner, prop, metadata); else if ([Tag.NUMBER, Tag.STRING].includes(tagOf(value))) cloned = assign(new Value(value), parentOrAssigner, prop, metadata); else if (Tag.SYMBOL === tagOf(value)) { cloned = assign( Object(Symbol.prototype.valueOf.call(value)), parentOrAssigner, prop, metadata); } else if (Tag.REGEXP === tagOf(value)) { const regExp = new Value(value.source, /\w*$/.exec(value)); regExp.lastIndex = value.lastIndex; cloned = assign(regExp, parentOrAssigner, prop, metadata); } else if (Tag.ERROR === tagOf(value)) { const cause = value.cause; cloned = assign(cause === undefined ? new Value(value.message) : new Value(value.message, { cause }), parentOrAssigner, prop, metadata); } else if (Tag.ARRAYBUFFER === tagOf(value)) { // copy data over to clone const arrayBuffer = new Value(value.byteLength); new Uint8Array(arrayBuffer).set(new Uint8Array(value)); cloned = assign(arrayBuffer, parentOrAssigner, prop, metadata); } // TypeArrays else if ([ Tag.DATAVIEW, Tag.FLOAT32, Tag.FLOAT64, Tag.INT8, Tag.INT16, Tag.INT32, Tag.UINT8, Tag.UINT8CLAMPED, Tag.UINT16, Tag.UINT32, Tag.BIGINT64, Tag.BIGUINT64 ].includes(tagOf(value))) { // copy data over to clone const buffer = new value.buffer.constructor( value.buffer.byteLength); new Uint8Array(buffer).set(new Uint8Array(value.buffer)); cloned = assign( new Value(buffer, value.byteOffset, value.length), parentOrAssigner, prop, metadata); } else if (Tag.MAP === tagOf(value)) { const map = new Value; cloned = assign(map, parentOrAssigner, prop, metadata); value.forEach((subValue, key) => { queue.push({ value: subValue, parentOrAssigner: cloned => { isExtensibleSealFrozen.push([subValue, cloned]); map.set(key, cloned) } }); }); } else if (Tag.SET === tagOf(value)) { const set = new Value; cloned = assign(set, parentOrAssigner, prop, metadata); value.forEach(subValue => { queue.push({ value: subValue, parentOrAssigner: cloned => { isExtensibleSealFrozen.push([subValue, cloned]); map.set(key, cloned) } }); }); } else throw warn("Attempted to clone unsupported type."); } } catch(error) { error.message = "Encountered error while attempting to clone " + "specific value. The value will be \"cloned\" " + "into an empty object. Error encountered: " + error.message log(warn(error.message, error.cause)); cloned = assign({}, parentOrAssigner, prop, metadata); // We don't want the prototype if we failed and set the value to an // empty object. ignoreProto = true; } cloneStore.set(value, cloned); isExtensibleSealFrozen.push([value, cloned]); // Ensure clone has prototype of value if (ignoreProto !== true && Object.getPrototypeOf(cloned) !== Object.getPrototypeOf(value)) Object.setPrototypeOf(cloned, Object.getPrototypeOf(value)); if (ignoreProps === true) continue; // Now copy all enumerable and non-enumerable properties. [Object.getOwnPropertyNames(value), Object.getOwnPropertySymbols(value)] .flat() .forEach(key => { queue.push({ value: value[key], parentOrAssigner: cloned, prop: key, metadata: Object.getOwnPropertyDescriptor(value, key) }); }); } // Check extensible, seal, and frozen statuses. isExtensibleSealFrozen.forEach(([value, cloned]) => { if (!Object.isExtensible(value)) Object.preventExtensions(cloned); if (Object.isSealed(value)) Object.seal(cloned); if (Object.isFrozen(value)) Object.freeze(cloned); }); return result; } function cloneDeep(value, options) { if (typeof options === "function") options = { customizer: options }; else if (typeof options !== "object") options = {}; let { customizer, log, logMode, letCustomizerThrow } = options; if (logMode !== "string" || typeof log === "function"); else if (logMode.toLowerCase() === "silent") log = () => { /* no-op */ }; else if (logMode.toLowerCase() === "quiet") log = error => console.warn(error.message); return cloneInternalNoRecursion(value, customizer, log, letCustomizerThrow); } var myObject = {}; let next = myObject; for (let i = 0; i < 1000; i++) { next.b = {}; next = next.b; } let myCopy;
Tests:
Lodash cloneDeep
myCopy = _.cloneDeep(myObject);
Native structuredClone
myCopy = structuredClone(myObject);
mt cloneDeep
myCopy = cloneDeep(myObject);
Rendered benchmark preparation results:
Suite status:
<idle, ready to run>
Run tests (3)
Previous results
Fork
Test case name
Result
Lodash cloneDeep
Native structuredClone
mt cloneDeep
Fastest:
N/A
Slowest:
N/A
Latest run results:
Run details:
(Test run date:
5 months ago
)
User agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Browser/OS:
Chrome 142 on Windows
View result in a separate tab
Embed
Embed Benchmark Result
Test name
Executions per second
Lodash cloneDeep
1475.2 Ops/sec
Native structuredClone
1498.1 Ops/sec
mt cloneDeep
268.2 Ops/sec
Autogenerated LLM Summary
(model
llama3.2:3b
, generated one year ago):
I'm here to help! It appears that we have three benchmark results from different libraries: 1. `Lodash cloneDeep` 2. `Native structuredClone` (from the Web API) 3. `mt cloneDeep` (another cloning library) To provide a helpful answer, I'll need to know what you'd like me to do with these benchmark results. Do you want me to: A) Compare the execution speeds of these libraries B) Analyze the differences in the implementation details between these libraries C) Provide suggestions on which library might be more suitable for your specific use case Let me know, and I'll do my best to assist you!
Related benchmarks:
Lodash cloneDeep vs structuredClone with map and hashset
Lodash cloneDeep vs structuredClone vs Custom Implementation
_.cloneDeep vs structuredClone vs my cloneDeep
Lodash cloneDeep vs structuredClone vs JSON Parse (100 000 objects)
Comments
Confirm delete:
Do you really want to delete benchmark?