Example of a Circular Reference in JavaScript

Example of a circular reference in Javascript?

A simple way to create a circular reference is to have an object that refers to itself in a property:

function Foo() {
this.abc = "Hello";
this.circular = this;
}

var foo = new Foo();
alert(foo.circular.circular.circular.circular.circular.abc);

Here the foo object contains a reference to itself.

With closures this is usually more implicit, by just having the circular reference in scope, not as an explicit property of some object:

var circular;

circular = function(arg) {
if (arg) {
alert(arg);
}
else {
// refers to the |circular| variable, and by that to itself.
circular("No argument");
}
}

circular("hello");
circular();

Here the function saved in circular refers to the circular variable, and thereby to itself. It implicitly holds a reference to itself, creating a circular reference. Even if circular now goes out of scope, it is still referenced from the functions scope. Simple garbage collectors won't recognize this loop and won't collect the function.

Is circular reference between objects a bad practice?

This kind of code will not cause memory leaks with today's browsers; as mentioned on MDN all major browsers have been shipping with mark-and-sweep GCs (that can handle cycles just fine) for some time now (e.g. Firefox itself has had a cycle collector since version 3).

From an architectural standpoint, this kind of code introduces moderately tight coupling between the two objects (if one changes in even a minor way, the other needs to be reviewed to determine if it needs to change as well) and should consequently be avoided if possible. But there is nothing inherently wrong with it.

How to detect circular references in JavaScript

Taking into an account the ideas from the comments I came to this function. It traverses the passed object (over arrays and object) and returns an array of paths that point to the circular references.

// This function is going to return an array of paths// that point to the cycles in the objectconst getCycles = object => {    if (!object) {        return;    }
// Save traversed references here const traversedProps = new Set(); const cycles = [];
// Recursive function to go over objects/arrays const traverse = function (currentObj, path) { // If we saw a node it's a cycle, no need to travers it's entries if (traversedProps.has(currentObj)) { cycles.push(path); return; }
traversedProps.add(currentObj);
// Traversing the entries for (let key in currentObj) { const value = currentObj[key]; // We don't want to care about the falsy values // Only objects and arrays can produce the cycles and they are truthy if (currentObj.hasOwnProperty(key) && value) { if (value.constructor === Object) { // We'd like to save path as parent[0] in case when parent obj is an array // and parent.prop in case it's an object let parentIsArray = currentObj.constructor === Array; traverse(value, parentIsArray ? `${path}[${key}]` : `${path}.${key}`);
} else if (value.constructor === Array) { for (let i = 0; i < value.length; i += 1) { traverse(value[i], `${path}.${key}[${i}]`); } }
// We don't care of any other values except Arrays and objects. } } }
traverse(object, 'root'); return cycles;};

Javascript Deep Clone Object with Circular References

There is now structuredClone in the Web API which also works with circular references.


Previous answer

I would suggest to use a Map to map objects in the source with their copy in the destination. In fact, I ended up using WeakMap as suggested by Bergi. Whenever a source object is in the map, its corresponding copy is returned instead of recursing further.

At the same time some of the code in the original deepClone code can be optimised further:

  • The first part testing for primitive values has a small issue: it treats new Number(1) differently from new Number(2). This is because of the == in the first if. It should be changed to ===. But really, the first few lines of code seem then equivalent to this test: Object(obj) !== obj

  • I also rewrote some for loops into more functional expressions

This needs ES6 support:

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

How does a circular object reference work in JavaScript?

var circular is (because it uses the var keyword) hoisted and declares a variable in the current scope as the scope is created (i.e. when the function or global scope is entered). It starts out with a value of undefined

{ ... } creates a new object and evaluates as a reference to it.

circular = { ... } takes that reference and assigns it to circular.

The assignment doesn't take place until after the object literal syntax has been evaluated.

If you try to read the value of circular inside the object literal syntax, it will be undefined because you are doing so before the assignment happens.


In the second example, the object exists and the reference has been assigned to circular before you try to read the value of circular. The object can then be modified to add a new property whose value is that reference.

How to fix circular reference in javascript object?

You can't "fix" that in a sense if you can't change the underlying data structure of the objects you store but you can wrap the serialization with try/catch to save your program from crashing if not all of them have circular references.
The reason why you get this issue is that one of the objects you pass as a second argument to addObj has a circular reference(s) to itself or to other objects that point to that object.
For example:

const obj = {prop: 42};
const anotherObj = {prop: 24};
anotherObj.someRef = obj;
obj.someRef = anotherObj;
const list = new List();
list.addObj('some', obj);

Consequently, we get a runtime error saying we have circular references:

Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'someRef' -> object with constructor 'Object'
--- property 'someRef' closes the circle

That's because JSON can't serialize cyclic data structures by design.

If that console.log statement has been added just for the debugging purposes, please, don't do that and use breakpoints in ChromeDevTools.

How can I print a circular structure in a JSON-like format?

Use JSON.stringify with a custom replacer. For example:

// Demo: Circular reference
var circ = {};
circ.circ = circ;

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(circ, (key, value) => {
if (typeof value === 'object' && value !== null) {
// Duplicate reference found, discard key
if (cache.includes(value)) return;

// Store value in our collection
cache.push(value);
}
return value;
});
cache = null; // Enable garbage collection

The replacer in this example is not 100% correct (depending on your definition of "duplicate"). In the following case, a value is discarded:

var a = {b:1}
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o, ...);

But the concept stands: Use a custom replacer, and keep track of the parsed object values.

As a utility function written in es6:

// safely handles circular references
JSON.safeStringify = (obj, indent = 2) => {
let cache = [];
const retVal = JSON.stringify(
obj,
(key, value) =>
typeof value === "object" && value !== null
? cache.includes(value)
? undefined // Duplicate reference found, discard key
: cache.push(value) && value // Store value in our collection
: value,
indent
);
cache = null;
return retVal;
};

// Example:
console.log('options', JSON.safeStringify(options))

Passing an object with circular references from server to client-side Javascript while retaining circularity

Douglas Crockford has a solution for this that I have successfully used to solve this problem before: Cycle.js

instead of just using stringify and parse you would first call decycle and restore with retrocycle

var jsonString = JSON.stringify(JSON.decycle(parent));
var restoredObject = JSON.retrocycle(JSON.parse(jsonString));

JSFiddle



Related Topics



Leave a reply



Submit