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 objects
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 thatinstead ofthisto 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.