带有循环引用的Javascript深克隆对象

我从Dmitriy Pichugin的现有答案中复制了下面的函数。 这个函数可以深入克隆一个没有任何循环引用的对象 – 它的工作。

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; } 

但是,我的程序无限期循环,我意识到这是由于循环引用。

循环引用的示例:

 function A() {} function B() {} var a = new A(); var b = new B(); ab = b; ba = a; 

我会build议使用地图来映射源中的对象与他们在目的地的副本。 事实上,我最终使用了WeakMap提出的WeakMap。 每当一个源对象在映射中时,它的相应副本将被返回而不是进一步recursion。

同时,原始deepClone代码中的deepClone代码可以进一步优化:

  • 原始值的第一部分testing有一个小问题:它将new Number(1)new Number(2)区别对待。 这是因为在第一个== if 。 应该更改为=== 。 但是真的,前几行代码看起来就等于这个testing: Object(obj) !== obj

  • 我也重写了一些for循环到更多的functionexpression式

这需要ES6支持:

 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 

您可以将引用和结果存储在单独的数组中,当您find具有相同引用的属性时,只需返回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); } 

我删除了地图和一些其他types的比较,使其更具可读性。

如果您可以定位现代浏览器,请检查@ trincot坚实的ES6答案。

由于对象克隆有许多缺陷(循环引用,原始链,Set / Map等)
我build议你使用一个经过充分testing的stream行解决scheme。

就像lodash的_.cloneDeep或' clone'npm 模块一样 。