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

时间:2016-10-27 18:44:43

标签: javascript node.js clone circular-reference

我已经从Dmitriy Pichugin的existing answer复制了以下功能。这个函数可以深度克隆一个没有任何循环引用的对象 - 它可以工作。

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();

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

5 个答案:

答案 0 :(得分:7)

我建议使用Map将源中的对象映射到目标中。事实上,我最终使用了Bergi建议的WeakMap。只要源对象在映射中,就会返回其相应的副本,而不是进一步递归。

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

  • 对原始值的第一部分测试存在一个小问题:它以new Number(1)new Number(2)区别对待==。这是因为第一个if中的===。它应该更改为Object(obj) !== obj。但实际上,前几行代码似乎等同于此测试:for

  • 我还将一些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循环重写为更多功能表达式

这需要ES6支持:

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {...}

答案 1 :(得分:2)

由于对象克隆存在很多陷阱(循环引用,原型链,Set / Map等) 我建议你使用一个经过充分测试的流行解决方案。

赞,lodash's _.cloneDeep'clone' npm module

答案 2 :(得分:0)

您可以将引用和结果存储在单独的数组中,当您找到具有相同引用的属性时,您只需要返回缓存的结果。

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

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

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

答案 3 :(得分:0)

const cloneDeep = src => {
  const clones = new WeakMap()
  return (function baseClone(src) {
    if (src !== Object(src)) {
      return src
    }
    if (clones.has(src)) {
      return clones.get(src)
    }
    const clone = {}
    clones.set(src, clone)
    Object.entries(src).forEach(([k, v]) => (clone[k] = baseClone(v)))
    return clone
  })(src)
}

const a = { x: 1 }
a.y = a
console.log(cloneDeep(a)) // <ref *1> { x: 1, y: [Circular *1] }

答案 4 :(得分:0)

基本思想是要避免“超出最大调用堆栈”的情况。

为了做到这一点,当需要克隆一个对象时,您需要首先创建一个空对象,缓存它(使用类似 WeakMap 的东西),然后更新该缓存对象的属性。如果您在创建副本后尝试缓存它,那么在它再次被递归引用时它不会被缓存(因此调用堆栈错误)。

您想要执行以下操作:

function deepClone(value, cache = new WeakMap()) {
  if(cache.has(value)) {
    return cache.get(value)
  }
  if(isPrimitive(value) || isFunction(value)) {
    return value;
  }
  if(value instanceof AnyParticularClass) {
    // handle specially in any cases you plan to support
  }
  if(isObject(value)) {
    const result = {}
    cache.set(value, result);
    Object.entries(value).forEach(function([key, val]) {
      result[key] = deepClone(val, cache);
    })
    return result
  }
  if(Array.isArray(value)) {
    return value.map(item => cloneDeep(item, cache));
  }
}

此外,克隆一个函数是没有意义的,所以只需将其返回即可。需要处理类的处理实例

相关问题