javascriptdeep-copykineticjs

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. i.e. there's no programmatic way of inspecting whether an object came from a "this" reference, is there?

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
                }

Solution

  • 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. 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. While the Array-collection bounds are O(n^2) this is not as bad as it sounds:

    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. 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. However, even in this case, n is only the number of unique non-primitive values traversed.

    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.)

    Happy coding.