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
Since object cloning has a lot of pitfalls (circular references, proto chains, Set/Map, etc)
I suggest you to use one of well-tested popular solutions.
Like, lodash's _.cloneDeep or 'clone' npm module.
You can store the references and results in separate arrays and when you find a property with the same reference you just need to return the cached result.
function deepClone(o) {
var references = [];
var cachedResults = [];
function clone(obj) {
if (typeof obj !== 'object')
return obj;
var index = references.indexOf(obj);
if (index !== -1)
return cachedResults[index];
references.push(obj);
var result = Array.isArray(obj) ? [] :
obj.constructor ? new obj.constructor() : {};
cachedResults.push(result);
for (var key in obj)
if (obj.hasOwnProperty(key))
result[key] = clone(obj[key]);
return result;
}
return clone(o);
}
I removed the map and some of the other type comparisons to make it more readable.
Check @trincot's solid ES6 answer if you can target modern browsers.