Javascript deep clone object with circular links

I copied the function below from the existing answer by Dmitry Pichugin. This function can deeply 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 loop is endless, and I realized that this is due to the circular link.

Circular link example:

 function A() {} function B() {} var a = new A(); var b = new B(); ab = b; ba = a; 
+5
source share
3 answers

I would suggest using a map to map objects in the source to their copy at the destination. In fact, I ended up using WeakMap as suggested by Bergi. Whenever the source object is on the map, its corresponding copy is returned, not recursion.

At the same time, some of the code in the deepClone source code can be further optimized:

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

  • I also rewrote several for loops in more functional expressions

This requires 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(); ab = b; ba = a; // Test it var c = deepClone(a); console.log('a' in cbab); // true 
+5
source

You can store links and results in separate arrays, and when you find a property with the same link, you just need to return the result in caching.

 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 deleted the map and some other type comparisons to make it more readable.

Check ES6's robust response to @trincot if you can target modern browsers.

0
source

Since cloning objects has many pitfalls (circular links, proto-chains, Set / Map, etc.)
I suggest you use one of the proven popular solutions.

Like, lodash _.cloneDeep or the 'clone' npm module .

-1
source

Source: https://habr.com/ru/post/1258908/


All Articles