HTML Preparation code:
AخA
 
1
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'></script>
Script Preparation code:
x
 
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>

Previous results

Experimental features:

  • Test case name Result
    Lodash cloneDeep
    Native structuredClone
    mt cloneDeep

    Fastest: N/A

    Slowest: N/A

Latest run results:
Run details: (Test run date: one month ago)
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Chrome 133 on Mac OS X 10.15.7
View result in a separate tab
Test name Executions per second
Lodash cloneDeep 3915.8 Ops/sec
Native structuredClone 5390.4 Ops/sec
mt cloneDeep 1003.2 Ops/sec