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
}
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 catchJSON.stringify
exceptions to see if there are back references, like this:It's just an idea and I have no idea of the performances. I fear it may be quite slow on large objects.
OK, I've got interested in how that "visited" property @pst mentioned could look like, so I've coded this:
Note, this is only an example. It does not support:
new Object
! This includes functions!instanceof Array
.Goodies:
Drawbacks:
__getDeepCircularCopy__
method that does not what it claims. 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":
The structure of trees that share a leaf won't be copied, they will become two independent leafes:
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:
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.