超出最大调用堆栈大小 - 没有明显的递归

Posted

技术标签:

【中文标题】超出最大调用堆栈大小 - 没有明显的递归【英文标题】:maximum call stack size exceeded - no apparent recursion 【发布时间】:2013-12-26 02:54:07 【问题描述】:

我花了大约 12 个小时来查看这段代码并摆弄它,试图找出递归问题的所在,因为我得到了“超出最大调用堆栈大小”的错误,但没有找到了。请比我聪明的人帮助我!

到目前为止,我发现当我创建对象spotcircle 对象时,问题消失了,但是当我将其设置为“pip”时,我得到了这个堆栈溢出错误。我已经用该死的显微镜检查了 pip 课程,但仍然不知道为什么会发生这种情况!

var canvas = document.getElementById('myCanvas');

//-------------------------------------------------------------------------------------
// Classes
//-------------------------------------------------------------------------------------
//=====================================================================================
//CLASS - point
function point(x,y)
    this.x = x;
    this.y = y;

//=====================================================================================
// CLASS - drawableItem
function drawableItem() 
    var size = 0;
    this.center = new point(0,0);
    this.lineWidth = 1;
    this.dependentDrawableItems = new Array();

//returns the size
drawableItem.prototype.getSize = function getSize()
    return this.size;

// changes the size of this item and the relative size of all dependents
drawableItem.prototype.changeSize = function(newSize)
    var relativeItemSizes = new Array;
    relativeItemSizes.length = this.dependentDrawableItems.length;
    // get the relative size of all dependent items
    for (var i = 0; i < this.dependentDrawableItems.length; i++)
        relativeItemSizes[i] = this.dependentDrawableItems[i].getSize() / this.size;
    
    // change the size
    this.size = newSize;
    // apply the ratio of change back to all dependent items
    for (var i = 0; i < relativeItemSizes.length; i++)
        this.dependentDrawableItems[i].changeSize(relativeItemSizes[i] * newSize);
    

//moves all the vertices and every dependent to an absolute point based on center
drawableItem.prototype.moveTo = function(moveX,moveY)
    //record relative coordinates
    var relativeItems = new Array;
    relativeItems.length = this.dependentDrawableItems.length;
    for (var i = 0; i < relativeItems.length; i++)
        relativeItems[i] = new point;
        relativeItems[i].x = this.dependentDrawableItems[i].center.x - this.center.x;
        relativeItems[i].y = this.dependentDrawableItems[i].center.y - this.center.y;
    
    //move the center
    this.center.x = moveX;
    this.center.y = moveY;
    //move all the items relative to the center
    for (var i = 0; i < relativeItems.length; i++)
        this.dependentDrawableItems[i].moveItemTo(this.center.x + relativeItems[i].x,
            this.center.y + relativeItems[i].y);
    

// draws every object in dependentDrawableItems
drawableItem.prototype.draw = function(ctx)
    for (var i = 0; i < this.dependentDrawableItems.length; i++) 
        this.dependentDrawableItems[i].draw(ctx);
    


//=====================================================================================
//CLASS - circle
function circle(isFilledCircle)
    drawableItem.call(this);
    this.isFilled = isFilledCircle

circle.prototype = new drawableItem();
circle.prototype.parent = drawableItem.prototype;
circle.prototype.constructor = circle;
circle.prototype.draw = function(ctx)
    ctx.moveTo(this.center.x,this.center.y);
    ctx.beginPath();
    ctx.arc(this.center.x, this.center.y, this.size, 0, 2*Math.PI);
    ctx.closePath();
    ctx.lineWidth = this.lineWidth;
    ctx.strokeStyle = this.outlineColor;
    if (this.isFilled === true)
        ctx.fill();
    else 
        ctx.stroke();
    
    this.parent.draw.call(this,ctx);


//=====================================================================================
//CLASS - pip
function pip(size)
    circle.call(this,true);

pip.prototype = new circle(false);
pip.prototype.parent = circle.prototype;
pip.prototype.constructor = pip;

//----------------------------------------------------------------------
// Objects/variables - top layer is last (except drawable area is first)
//----------------------------------------------------------------------
var drawableArea = new drawableItem();

var spot = new pip();
spot.changeSize(20);
drawableArea.dependentDrawableItems[drawableArea.dependentDrawableItems.length] = spot;

//------------------------------------------
// Draw loop
//------------------------------------------
function drawScreen() 
    var context = canvas.getContext('2d');
    context.canvas.width  = window.innerWidth;
    context.canvas.height = window.innerHeight;

    spot.moveTo(context.canvas.width/2, context.canvas.height/2);

    drawableArea.draw(context);


window.addEventListener('resize', drawScreen);

这是演示:http://jsfiddle.net/DSU8w/

【问题讨论】:

还不够好,无法回答,但我偷偷怀疑你有一些非构造函数试图充当构造函数。 Google 闭包引发了一些关于 Property getSize never defined 的问题,并且一些函数不是构造函数!看看***.com/a/5991265/588079 和***.com/a/4613017/588079 和***.com/q/6095530/588079。看看这是否有帮助,否则请尝试剪切部分代码,直到你有最低限度的溢出并在小提琴中分享它。 WARNING(对于其他专家):这个 sn-p 可能会使您的浏览器崩溃(我的浏览器崩溃了两次),所以不要摆弄如果您有重要的东西打开/未备份(例如其他 jsfiddle)。 @jgrant:尽量不要让小提琴自动运行,而是在点击按钮等清晰的用户操作后自动运行,从而让人们有机会实际加载小提琴并在加载崩溃之前检查源代码。 我猜是“drawableItem.prototype.changeSize”导致了你的问题。 @GitaarLAB - getSize 错误来自我删除的缺失方法,以及数百行其他代码,试图将代码缩减到最低限度 Aadit - 感谢您添加小提琴。 【参考方案1】:
this.parent.draw.call(this,ctx);

是你的问题。在pip 对象上,父对象将是circle.prototype。所以当你现在调用spot.draw()时,它会调用spot.parent.draw.call(spot),其中this.parent仍然是circle.prototype...

您需要从circle.prototype.draw 显式调用drawableItem.prototype.draw.call(this)。顺便说一句,你应该not use new for the prototype chain。

【讨论】:

我开始研究为什么不使用“新”和替代品......现在我的大脑正在融化。【参考方案2】:

你为什么要写这样的代码?很难理解和调试。当我创建很多类时,我通常使用augment 来构建我的代码。这就是我将重写您的代码的方式:

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

使用augment,您可以干净地创建类。例如,您的 drawableItem 类可以重组如下:

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);
    ;
);

继承也很简单。例如,您可以重组 circlepip 类,如下所示:

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);
    ;
);

现在您已经创建了所有类,您终于可以开始画图了:

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);

我们完成了。亲自观看演示:http://jsfiddle.net/b5vNk/

我们不仅使您的代码更具可读性、可理解性和可维护性,而且还解决了您的递归问题。

正如 Bergi 所说,问题出在 circle.prototype.draw 函数中的声明 this.parent.draw.call(this,ctx) 上。因为spot.parentcircle.prototype,所以this.parent.draw.call(this,ctx) 语句等价于circle.prototype.draw.call(this,ctx)。如您所见,circle.prototype.draw 函数现在递归调用自身,直到超过最大递归深度并引发错误。

augment 库优雅地解决了这个问题。当您扩充一个类 augment 时,不必在每个原型上创建一个 parent 属性,而是为您提供该类的 prototype 作为参数(我们称之为 base):

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

base 参数应被视为常量。因为它是上面Circle 类中的常量base.draw.call(this, context) 将始终等价于DrawableItem.prototype.draw.call(this, context)。因此,您将永远不会有不需要的递归。与this.parent 不同,base 参数将始终指向正确的原型。

【讨论】:

【参考方案3】:

Bergi 的回答是正确的,如果您不想多次硬编码父名称,可以使用辅助函数来设置继承:

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();

对于 Object.create 上的 polyfill,请查看 here。

【讨论】:

以上是关于超出最大调用堆栈大小 - 没有明显的递归的主要内容,如果未能解决你的问题,请参考以下文章

Python递归限制与堆栈大小?

在我的文件中找不到任何导致 Uncaught RangeError: 超出最大调用堆栈的递归

为啥这个递归函数超过调用堆栈大小?

找到最大递归深度

Maximum call stack size exceeded

有没有办法在递归调用之前检查可用的堆栈大小? (C#)