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;
I would suggest to use a Map to map objects in the source with their copy in the destination. In fact, I ended up using WeakMap
as suggested by Bergi. 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 WeakMap()) {
// Do not try to clone primitives or functions
if (Object(obj) !== obj || obj instanceof Function) return obj;
if (hash.has(obj)) return hash.get(obj); // Cyclic reference
try { // Try to run constructor (without arguments, as we don't know them)
var result = new obj.constructor();
} catch(e) { // Constructor failed, create object without running the constructor
result = Object.create(Object.getPrototypeOf(obj));
}
// Optional: support for some standard constructors (extend as desired)
if (obj instanceof Map)
Array.from(obj, ([key, val]) => result.set(deepClone(key, hash),
deepClone(val, hash)) );
else if (obj instanceof Set)
Array.from(obj, (key) => result.add(deepClone(key, hash)) );
// Register in hash
hash.set(obj, result);
// Clone and assign enumerable own properties recursively
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