Lolums Lolums - 1 month ago 17
Node.js Question

Javascript Deep Clone Object with Circular References

I have copied the function below from an existing answer by Dmitriy Pichugin. This function can deep clone an object without any circular references- it works.

function deepClone( obj ) {
if( !obj || true == obj ) //this also handles boolean as true and false
return obj;
var objType = typeof( obj );
if( "number" == objType || "string" == objType ) // add your immutables here
return obj;
var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
if( obj instanceof Map )
for( var key of obj.keys() )
result.set( key, deepClone( obj.get( key ) ) );
for( var key in obj )
if( obj.hasOwnProperty( key ) )
result[key] = deepClone( obj[ key ] );
return result;
}


However, my program loops indefinitely and I have realised that this is due to a circular reference.

An example of a circular reference:

function A() {}
function B() {}

var a = new A();
var b = new B();

a.b = b;
b.a = a;

Answer

I would suggest to use a Map to map objects in the source with their copy in the destination. Whenever a source object is in the map, its corresponding copy is returned instead of recursing further.

At the same time some of the code in the original deepClone code can be optimised further:

  • The first part testing for primitive values has a small issue: it treats new Number(1) differently from new Number(2). This is because of the == in the first if. It should be changed to ===. But really, the first few lines of code seem then equivalent to this test: Object(obj) !== obj

  • I also rewrote some for loops into more functional expressions

This needs ES6 support:

function deepClone(obj, hash = new Map()) {
    if (Object(obj) !== obj) return obj; // primitives
    if (hash.has(obj)) return hash.get(obj); // cyclic reference
    var result = Array.isArray(obj) ? []
               : obj.constructor ? new obj.constructor() : {};
    hash.set(obj, result);
    if (obj instanceof Map)
        Array.from(obj, ([key, val]) => result.set(key, deepClone(val, hash)) );
    return Object.assign(result, ...Object.keys(obj).map (
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}

// sample data
function A() {}
function B() {}

var a = new A();
var b = new B();

a.b = b;
b.a = a;

// test it
var c = deepClone(a);

console.log('a' in c.b.a.b); // true

Comments