Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Friday, February 15, 2008

Too Much Recursion From Extending Objects

The other day I spent a couple hours beating my head, wondering why I was getting too much recursion in my objects. My project had 3 objects, where object C extended object B, and B extended object A. Each Object had a Function ‘update’, and the first statement inside ‘update’ was to call the parent object’s ‘update’ Function.

Example 1: Call to Function in Parent Object

.update(node) { this.parent.update.call(this, node); … },

I have been using “Core.extend” for a while and referencing the parent Object, but I had never tried to extend a child Object, while Function chain to the super class of the super class. In simpler terms, While I have often created an Object B extending an Object A, with a Function (such as ‘update’) that called the same Function in the parent Object A. I had never then also created an Object C extending Object B, which called a Function in B that then called a Function in A. For example:

Example 2: C Extends B Extends A

var A = function() {}; var B = function() {}; var C = function() {}; A.prototype = { update: function(obj) { // some logic } }; B.prototype = { update: function(obj) { this.parent.update.call(this, obj); // some logic } }; C.prototype = { update: function(obj) { this.parent.update.call(this, obj); // some logic } }; Core.extend(B, A); Core.extend(C, B);

Initially, this looks correct, as it is the same pattern that one always uses when extending and calling a Function on the parent Object. However, in this example, if you call the ‘update’ Function of C, you will get a “Too Much Recursion” error, or simply crash your browser. Why?

Well, when you call the parent Function, you generally pass in the scope of the current Object, that way any operations in the parent Function are applied to the current Object and not the prototype of the parent Object. However, if Object C passes its scope to Object B, then when the ‘update’ Function of Object B, calls its parent, it is actually calling the parent of Object C (since we are executing B in the scope of Object C). This then, creates an infinite loop.

I spent some time thinking and searching the web, but could not find an elegant way to change the extend Function to handle these situations. In the end, I had to hack the way Object C calls its parent Object B, in order prevent the loop. Here is what I did:

Example 3: Fixing the Infinite Loop

var A = function() {}; var B = function() {}; var C = function() {}; A.prototype = { update: function(obj) { // some logic } }; B.prototype = { update: function(obj, scope) { var that = scope || this; this.parent.update.call(that, obj); // some logic; use variable ‘that’ instead of ‘this’ to reference self } }; C.prototype = { update: function(obj) { this.parent.update.call(this.parent, obj, this); // some logic } }; Core.extend(B, A); Core.extend(C, B);

In these examples, each of the ‘update’ Functions expect a parameter ‘obj’. I have included this to illustrate that parameters can easily be passed into the Function chain. In my examples I have included only 1 parameter, but you could set the Functions up to accept no parameters or as many parameters as you like. However, to fix the scoping issue of Object B, the last parameter must be an optional ’scope’ variable.

With this technique, if you are using a non-extended instance of Object B, you can simply call the ‘update’ Function with the desired parameters and it works fine. Yet when you call the ‘update’ Function of Object B from its child (Object C), then the last parameter ’scope’ is passed and used as the operational scope inside the ‘update’ Function of Object B. Inside Object B, you will need to use the ‘that’ variable every time you mean ‘this’, except when calling the ‘update’ Function of its parent (Object A), as ‘that’ holds the true (operational) scope of the Function. Object C calls the ‘update’ Function of Object B with the execution scope of its parent (Object B). So the ‘update’ Function of Object B executes with the scope of Object B (allow access to the parent of Object B), while using the operational scope of Object C, stored in ‘that’. In this way, Object B can call its parent Object A, but we can manipulate the instance of Object C throughout the Function chain.

posted by Matt Snider at 11:23 am  

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress