简体   繁体   English

在 Javascript 中,执行深度复制时,由于属性为“this”,如何避免循环?

[英]In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being “this”?

I have some library code that is cycling endlessly on me.我有一些库代码在我身上无休止地循环。

I'm not clear on how to best perform cycle detection and avoidance in javascript.我不清楚如何最好地在 javascript 中执行循环检测和避免。 ie there's no programmatic way of inspecting whether an object came from a "this" reference, is there?即没有检查对象是否来自“this”引用的编程方式,是吗?

Here's the code.这是代码。 Thanks!谢谢!

setAttrs: function(config) {
    var go = Kinetic.GlobalObject;
    var that = this;

    // set properties from config
    if(config !== undefined) {
        function setAttrs(obj, c) {
            for(var key in c) {
                var val = c[key];

                /*
                 * if property is an object, then add an empty object
                 * to the node and then traverse
                 */
                if(go._isObject(val) && !go._isArray(val) && !go._isElement(val)) {
                    if(obj[key] === undefined) {
                        obj[key] = {};
                    }
                    setAttrs(obj[key], val);  // <--- offending code; 
                                              // one of my "val"s is a "this" reference
                                              // to an enclosing object
                }

The "reliable and clean" way I know of to deal with this situation is to use a collection of "visited" objects and then react -- terminate, insert symbolic reference, etc -- based on if the current object has already been "visited" or not.我所知道的处理这种情况的“可靠和干净”的方法是使用“访问过的”对象的集合,然后根据当前对象是否已经“访问过”做出反应——终止、插入符号引用等“ 或不。

Mr. Crockford uses this approach in cycle.js and he uses an Array for the collection. Crockford 先生在cycle.js 中使用了这种方法,并且他使用了一个数组作为集合。 Excerpt:摘抄:

// If the value is an object or array, look to see if we have already
// encountered it. If so, return a $ref/path object. This is a hard way,
// linear search that will get slower as the number of unique objects grows.

for (i = 0; i < objects.length; i += 1) {
    if (objects[i] === value) {
        return {$ref: paths[i]};
    }
}

It is unfortunately not possible to use a primitive "Hash" approach for this in JavaScript because it lacks an Identity-Map.不幸的是,无法在 JavaScript 中使用原始的“哈希”方法,因为它缺少 Identity-Map。 While the Array-collection bounds are O(n^2) this is not as bad as it sounds :虽然数组集合的边界是O(n^2)但这并不像听起来那么糟糕

This is because, if the "visited" collection is just a guard then the value of n is just the depth of the stack: only cycles are of importance while copying the same object multiple times is not.这是因为,如果“已访问”集合只是一个守卫,那么n的值就是堆栈的深度:只有循环才是重要的,而多次复制同一个对象则不是。 That is, the objects in the "visited" collection can be pruned on stack-unwind.也就是说,“已访问”集合中的对象可以在堆栈展开时修剪。

In the cycle.js code the "visited" collection cannot be pruned because it must ensure the same symbolic name for a given object is always used which allows the serialization to "maintain the references" when it is restored.在 cycle.js 代码中,“已访问”集合不能被修剪,因为它必须确保始终使用给定对象的相同符号名称,这允许序列化在恢复时“维护引用”。 However, even in this case, n is only the number of unique non-primitive values traversed.然而,即使在这种情况下, n只是遍历的唯一非原始值的数量。

The only other method I can think of would require adding a "visited property" directly to the objects being traversed, which I would consider a generally undesirable feature.我能想到的唯一其他方法需要直接向正在遍历的对象添加“已访问属性”,我认为这是一个通常不受欢迎的功能。 (However, see Bergi's comment about this artifact being [relatively] easily cleaned up.) (但是,请参阅 Bergi 关于此工件 [相对] 容易清理的评论。)

Happy coding.快乐编码。

OK, I've got interested in how that "visited" property @pst mentioned could look like, so I've coded this:好的,我对@pst 提到的“已访问”属性的外观很感兴趣,所以我编写了以下代码:

Object.copyCircular = function deepCircularCopy(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o))
        return o; // primitive value
    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function")
        return cache();
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = deepCircularCopy(o[i]);
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = deepCircularCopy(o[prop]);
            else if (set)
                result[prop] = deepCircularCopy(cache);
    }
    if (set)
        o[gdcc] = cache; // reset
    else
        delete o[gdcc]; // unset again
    return result;
};

Note, this is only an example.请注意,这只是一个示例。 It does not support:它不支持:

  • non-plain objects.非平面物体。 Everything with a prototype (except arrays) will not be cloned but copied to a new Object !具有原型的所有内容(数组除外)都不会被克隆,而是会复制到new Object This includes functions!这包括功能!
  • cross-global-scope objects: it uses instanceof Array .跨全局范围的对象:它使用instanceof Array
  • property descriptors like setters/getters, unwritable and nonenumerable properties属性描述符,如 setter/getter、不可写和不可枚举的属性

Goodies:好东西:

  • it does not use a big array which it needs to search each time it encounters an object.它不使用每次遇到对象时都需要搜索的大数组。

Drawbacks:缺点:

  • does not work on objects which have a __getDeepCircularCopy__ method that does not what it claims.不适用于具有__getDeepCircularCopy__方法而不是它所声称的方法的对象。 Although methods (properties with a function as value) are not supported anyway in this lightweight version.尽管在这个轻量级版本中无论如何都不支持方法(具有函数值的属性)。

This solution will work on objects with circular references, copying the circular structure, without ending in an infinite loop .此解决方案将适用于具有循环引用的对象,复制循环结构,而不会以无限循环结束 Note that "circular" means by here that a property references one of its "parents" in the "tree":请注意,“循环”在这里表示一个属性在“树”中引用其“父”之一:

   [Object]_            [Object]_
     /    |\              /    |\
   prop     |           prop    |
     \_____/             |      |
                        \|/     |
                      [Object]  |
                          \     |
                         prop   |
                            \___/

The structure of trees that share a leaf won't be copied, they will become two independent leafes:共享一片叶子的树的结构不会被复制,它们将成为两个独立的叶子:

            [Object]                     [Object]
             /    \                       /    \
            /      \                     /      \
          |/_      _\|                 |/_      _\|  
      [Object]    [Object]   ===>  [Object]    [Object]
           \        /                 |           |
            \      /                  |           |
            _\|  |/_                 \|/         \|/
            [Object]               [Object]    [Object]

Not unless you want to keep track of every property copied.除非您想跟踪复制的每个属性,否则不会。

But if you're sure that every property is either null , a string, a number, an array or a simple object, you can catch JSON.stringify exceptions to see if there are back references, like this:但是,如果您确定每个属性都是null 、字符串、数字、数组或简单对象,则可以捕获JSON.stringify异常以查看是否存在反向引用,如下所示:

try {
    JSON.stringify(obj);
    // It's ok to make a deep copy of obj
} catch (e) {
    // obj has back references and a deep copy would generate an infinite loop
    // Or finite, i.e. until the stack space is full.
}

It's just an idea and I have no idea of the performances.这只是一个想法,我对表演一无所知。 I fear it may be quite slow on large objects.我担心它在大型物体上可能会很慢。

Here's a simple recursive clone method.这是一个简单的递归克隆方法。 Like many others solutions, most non-basic properties will share a reference with the original object (such as functions).像许多其他解决方案一样,大多数非基本属性将与原始对象(例如函数)共享一个引用。

It handles infinite loops by keeping a map of referenced objects so subsequent references can share the same clone.它通过保留引用对象的映射来处理无限循环,以便后续引用可以共享相同的克隆。

const georgeCloney = (originalObject, __references__ = new Map()) => {
  if(typeof originalObject !== "object" || originalObject === null) {
    return originalObject;
  }

  // If an object has already been cloned then return a
  // reference to that clone to avoid an infinite loop
  if(__references__.has(originalObject) === true) {
    return __references__.get(originalObject);
  }

  let clonedObject = originalObject instanceof Array ? [] : {};

  __references__.set(originalObject, clonedObject);

  for(let key in originalObject) {
    if(originalObject.hasOwnProperty(key) === false) {
      continue;
    }

    clonedObject[key] = georgeCloney(originalObject[key], __references__);
  }

  return clonedObject;
};

Example usage...示例用法...

let foo = {};
foo.foo = foo;

let bar = georgeCloney(foo);
bar.bar = "Jello World!";

// foo output
// {
//   foo: {
//     foo: {...}
//   }
// }
// 
// bar output
// { 
//   foo: { 
//     foo: {...},
//     bar: "Jello World!"
//   }, 
//   bar: "Jello World!"
// }

I had to do this for an interview and this is what I got:我必须这样做才能接受采访,这就是我得到的:

Object.defineProperty(Object.prototype, 'deepclone', { enumerable :false, configurable :true, value :function(){
  let map /*for replacement*/ =new Map(), rep ={} ;map.set(this, rep)
  let output = (function medclone(_, map){  let output ={..._}
    for(let k in _){  let v =_[k]
      let v2 ;if(!(v instanceof Object)) v2 =v ;else {
        if(map.has(v)) v2 =map.get(v) ;else {
          let rep ={}
          map.set(v, rep), v2 =medclone(v, map)
          Replace(rep, v2), map.set(v, v2)
        }
      }
      output[k] =v2
    }    return output
  })(this, map)
  Replace(rep, output)
  return output
  /*[*/ function Replace(rep/*resentative*/, proper, branch =proper, seens =new Set()){
    for(let k in branch){  let v =branch[k]
      if(v ===rep) branch[k] =proper
      else if(v instanceof Object &&!seens.has(v)) seens.add(v), Replace(rep, proper, v, seens)
    }
  }/*]*/
} })
                                                                                                                                  // Code is freely available for use for academia/scrutiny. As for development, contact author. 

The methodology is "hack and slash" instead of "plan with ample time before coding", so the result can't be all that great but it works.方法是“hack and slash”而不是“在编码之前有充足的时间计划”,所以结果不可能那么好,但它有效。 could iterate on it to remove the WET in the recursion entry point, for one.可以迭代它以删除递归入口点中的 WET,例如。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM