Skip to content Skip to sidebar Skip to footer

Maximum Call Stack Size Exceeded - No Apparent Recursion

I've spent about 12 hours looking through this code, and fiddling with it, trying to find out where there's a recursion problem because I'm getting the, 'maximum call stack size ex

Solution 1:

this.parent.draw.call(this,ctx);

is your problem. On a pip object, the parent will be circle.prototype. So when you now call spot.draw(), it will call spot.parent.draw.call(spot), where this.parent is still the circle.prototype

You will need to explicitly invoke drawableItem.prototype.draw.call(this) from circle.prototype.draw. Btw, you should not use new for the prototype chain.


Solution 2:

Why would you write code like that? It's so difficult to understand and debug. When I'm creating lots of classes I usually use augment to structure my code. This is how I would rewrite your code:

var Point = Object.augment(function () {
    this.constructor = function (x, y) {
        this.x = x;
        this.y = y;
    };
});

Using augment you can create classes cleanly. For example your drawableItem class could be restructured as follows:

var DrawableItem = Object.augment(function () {
    this.constructor = function () {
        this.size = 0;
        this.lineWidth = 1;
        this.dependencies = [];
        this.center = new Point(0, 0);
    };

    this.changeSize = function (toSize) {
        var fromSize = this.size;
        var ratio = toSize / fromSize;
        this.size = toSize;

        var dependencies = this.dependencies;
        var length = dependencies.length;
        var index = 0;

        while (index < length) {
            var dependency = dependencies[index++];
            dependency.changeSize(dependency.size * ratio);
        }
    };

    this.moveTo = function (x, y) {
        var center = this.center;
        var dx = x - center.x;
        var dy = y - center.y;
        center.x = x;
        center.y = y;

        var dependencies = this.dependencies;
        var length = dependencies.length;
        var index = 0;

        while (index < length) {
            var dependency = dependencies[index++];
            var center = dependency.center;

            dependency.moveTo(center.x + dx, center.y + dy);
        }
    };

    this.draw = function (context) {
        var dependencies = this.dependencies;
        var length = dependencies.length;
        var index = 0;

        while (index < length) dependencies[index++].draw(context);
    };
});

Inheritance is also very simple. For example you can restructure your circle and pip classes as follows:

var Circle = DrawableItem.augment(function (base) {
    this.constructor = function (filled) {
        base.constructor.call(this);
        this.filled = filled;
    };

    this.draw = function (context) {
        var center = this.center;
        var x = center.x;
        var y = center.y;

        context.moveTo(x, y);

        context.beginPath();
        context.arc(x, y, this.size, 0, 2 * Math.PI);
        context.closePath();

        context.lineWidth = this.lineWidth;
        context[this.filled ? "fill" : "stroke"]();
        base.draw.call(this, context);
    };
});

var Pip = Circle.augment(function (base) {
    this.constructor = function () {
        base.constructor.call(this, true);
    };
});

Now that you've created all your classes you can finally get down to the drawing:

window.addEventListener("DOMContentLoaded", function () {
    var canvas = document.getElementById("myCanvas");
    var context = canvas.getContext("2d");
    var drawableArea = new DrawableItem;
    var spot = new Pip;

    spot.changeSize(20);
    drawableArea.dependencies.push(spot);
    window.addEventListener("resize", drawScreen, false);

    drawScreen();

    function drawScreen() {
        var width = canvas.width = window.innerWidth;
        var height = canvas.height = window.innerHeight;
        spot.moveTo(width / 2, height / 2);
        drawableArea.draw(context);
    }
}, false);

We're done. See the demo for yourself: http://jsfiddle.net/b5vNk/

Not only have we made your code more readable, understandable and maintainable but we have also solved your recursion problem.

As Bergi mentioned the problem was with the statement this.parent.draw.call(this,ctx) in the circle.prototype.draw function. Since spot.parent is circle.prototype the this.parent.draw.call(this,ctx) statement is equivalent to circle.prototype.draw.call(this,ctx). As you can see the circle.prototype.draw function now calls itself recursively until it exceeds the maximum recursion depth and throws an error.

The augment library solves this problem elegantly. Instead of having to create a parent property on every prototype when you augment a class augment provides you the prototype of that class as a argument (we call it base):

var DerivedClass = BaseClass.augment(function (base) {
    console.log(base === BaseClass.prototype); // true
});

The base argument should be treated as a constant. Because it's a constant base.draw.call(this, context) in the Circle class above will always be equivalent to DrawableItem.prototype.draw.call(this, context). Hence you will never have unwanted recursion. Unlike this.parent the base argument will alway point to the correct prototype.


Solution 3:

Bergi's answer is correct, if you don't want to hard code the parent name multiple times you could use a helper function to set up inheritance:

function inherits(Child,Parent){
  Child.prototype=Object.create(Parent.prototype);
  Child.parent=Parent.prototype;
  Child.prototype.constructor=Child;
};
function DrawableItem() {
  this.name="DrawableItem";
}
DrawableItem.prototype.changeSize = function(newSize){
  console.log("changeSize from DrawableItem");
  console.log("invoking object is:",this.name);
}
function Circle(isFilledCircle){
    Circle.parent.constructor.call(this);
    this.name="Circle";//override name
}
inherits(Circle,DrawableItem);
Circle.prototype.changeSize = function(newSize){
  Circle.parent.changeSize.call(this);
  console.log("and some more from circle");
};
function Pip(size){
    Pip.parent.constructor.call(this,true);
    this.name="Pip";
}
inherits(Pip,Circle);

var spot = new Pip();
spot.changeSize();

For a polyfill on Object.create look here.


Post a Comment for "Maximum Call Stack Size Exceeded - No Apparent Recursion"