超出最大调用堆栈大小错误

745

我正在使用 Direct Web Remoting (DWR) JavaScript 库文件,但在 Safari(桌面和 iPad)上出现了错误。

它显示:

Maximum call stack size exceeded.

这个错误具体意味着什么?是否会完全停止处理?

此外,是否有任何修复方案适用于 Safari 浏览器?(实际上,在 iPad Safari 上,它显示:

JS:执行超时

我认为这是同样的调用栈问题。)


3
在使用 AJAX 中的 data 传递变量时(未声明变量),我遇到了这个错误。通过声明变量来修复了这个错误。 - phpfresher
无限循环... - mercury
对我来说,我的 JavaScript 函数名是 onclick(),然后我遇到了这个错误 :-) 尽管这些是保留名称。 - sbr
38个回答

825

这意味着在您的代码中,您调用了一个函数,该函数又调用另一个函数,以此类推,直到达到调用栈限制。

这几乎总是由于递归函数存在未满足的基本情况。

查看堆栈

考虑以下代码...

(function a() {
    a();
})();

以下是几次调用后的调用堆栈...

Web Inspector

可以看到,调用堆栈会增长,直到达到限制为止:浏览器硬编码的堆栈大小或内存耗尽。

要修复它,请确保您的递归函数具有基本情况,该基本情况能够得到满足...

(function a(x) {
    // The following condition 
    // is the base case.
    if ( ! x) {
        return;
    }
    a(--x);
})(10);

7
非常感谢。是否有任何方法/工具可以让我知道堆栈限制已经耗尽了?由于这是一个庞大的库文件,且不是由我创建的,因此我并不完全清楚问题可能出在哪里。 - copenndthagen
90
如果你需要运行一个函数近 300 兆次,那么 你很可能做错了什么。 - ceejayoz
4
@Oliver - 你可能需要了解动态规划 - 我猜想你没有那么多独特的解法,你正在重复步骤,因此你可能需要将其编程为多项式时间而不是二次时间。http://en.wikipedia.org/wiki/Dynamic_programming - newshorts
5
在普通函数中是这样的,但想想递归函数。它会调用另一个函数(它自己),并在堆栈上留下前一个函数的返回地址(否则计算机怎么知道该去哪里呢?)当然,这个函数又会调用自身,每次都将返回地址推入堆栈。如果由于一个不太可能在下个世纪内满足的基本情况而导致这些操作失控,你就会得到一个堆栈溢出。 - alex
5
你的代码重复执行了同一个低效的段落134217728次。说句实话,速度很慢。你应该使用位运算符,这样可以将9层循环展开为一层循环。 - Jack G
显示剩余11条评论

108

在我的情况下,我发送了输入元素而不是它们的值:

$.post( '',{ registerName: $('#registerName') } )

改为:

$.post( '',{ registerName: $('#registerName').val() } )

这导致我的Chrome浏览器标签页冻结,无法显示“等待/终止”对话框,即使页面不响应。


4
我想知道为什么这种错误没有例外...谢谢! - Džuris
1
非常感谢,这救了我一命。 - Eme Hado
我正要将这个作为可能的解决方案添加。我刚刚做了这件事情,哈哈。 - mbuchok
谢谢。这就是问题所在。 - Alexandru Burca
这正是发生的事情。在两种情况下,我抓取了输入对象而不是 .value。 - Chris
显示剩余3条评论

104

如果您不小心两次导入/嵌入相同的JavaScript文件,则有时会出现此问题。建议在检查器的资源选项卡中进行检查。


9
当你意外地两次导入/嵌入同一个JS文件时,通常会出现这个问题。具体导致递归循环的过程我不想去猜测,但我认为它就像是驾驶两辆相同的汽车以100英里/小时的速度通过同一收费站一样。 - lucygenik
4
我不认为情况是这样的。如果两个相同类型的函数或变量被嵌入,后面的定义会覆盖先前的定义。 - ssudaraka
是的...我不太清楚为什么会发生这种情况或者具体的过程是什么,我本以为它也会被覆盖掉。 - lucygenik
有点晚了,但我想当代码不在任何函数之外时,两个JS文件中的代码都会被执行,并且它们不会相互覆盖,因为它们都不在任何函数之内。 - Cillian Collins
是的,这正是我的问题。在不同的地方加载了相同的文件集。现在似乎已经解决了。感谢您指引我正确的方向! - gadgetroid
1
@lucygenik 下次在批准针对您的帖子的恶意编辑时要更加小心。请检查历史记录,此帖子差点被误判为垃圾邮件而被删除。 - Jean-François Fabre

37

你的代码中存在递归循环(即一个函数不断调用自身,直到堆栈满)。

其他浏览器要么有更大的堆栈(因此您会超时),要么由于某种原因(可能是糟糕的try-catch安排)吞下了错误。

使用调试器检查发生错误时的调用堆栈。


非常感谢您的回复。在IE / FF上,代码似乎运行正常。只有在桌面Safari和iPad Safari中,我才会收到错误提示。实际上,JS文件不是我自己创建的,而是一个库文件(来自DWR engine.js)..http://directwebremoting.org/另外,当您说“使用调试器检查调用堆栈”时,我应该如何在Safari Inspector中执行该操作? - copenndthagen
我没有使用Safari Inspector的经验。尝试打开JavaScript调试器并加载您的页面。当未捕获的错误被抛出时,调试器应该停止。如果无法正常工作,请在superuser或webmasters上咨询(请参见页面底部)。 - Aaron Digulla
有两个同名的函数!!糟糕! - RandomlyOnside

29
"The problem with detecting stack overflows is that sometimes the stack trace will unwind and you won't be able to see what's actually going on. I've found some of Chrome's newer debugging tools useful for this. Hit the Performance tab, make sure Javascript samples are enabled and you'll get something like this. It's pretty obvious where the overflow is here! If you click on extendObject you'll be able to actually see the exact line number in the code."

enter image description here

你可以查看时间,这可能有帮助,也可能是一个误导。

enter image description here


如果你找不到问题,另一个有用的技巧是在你认为问题出现的地方放置大量console.log语句。上面的步骤可以帮助你做到这一点。
在Chrome中,如果你重复输出相同的数据,它会像这样显示,更清楚地显示问题所在。在这种情况下,堆栈在最终崩溃之前达到了7152个帧:

enter image description here


1
对于阅读此答案的任何人。当然,它对我有效,并且我能够将其缩小到递归函数调用。我可以轻松地使用性能选项卡立即跟踪问题。这是由于在单独的应用程序中包含了2个角元素资产。一旦我禁用了其中一个元素,问题就消失了。希望这能帮助到某些人! - Saturn K

20

在我的情况下,我使用以下方法将一个大的字节数组转换为字符串:

String.fromCharCode.apply(null, new Uint16Array(bytes))

bytes 包含数百万条目,这太大了无法放在堆栈中。


我也遇到了同样的问题,同一行代码!!谢谢 - ksh
那么,将大字节数组转换的最佳解决方案是什么? - ksh
11
你需要使用for循环来完成操作,而不是一次性调用。以下是翻译后的代码:var uintArray = new Uint16Array(bytes); var converted = []; for (var i = 0; i < uintArray.length; i++) {converted.push(String.fromCharCode(uintArray[i]))}; - Nate Glenn
我该如何将其转换为JSON?我尝试了JSON.parse,但它没有起作用...我正在使用Uintl8Array。 - ksh
@KarthikHande,如果没有看到整个问题,我无法判断。请打开另一个问题并提供所有细节。 - Nate Glenn
我来这里添加这个答案。很高兴有人给出了超出递归过多的解释。@ksh 我知道这是一个非常古老的问题,但是你可以将数组分成批次,而不是为每个字节单独调用String.fromCharCode()。我认为这将是等效的:const batchSize = 4096; const strings = []; for (let i = 0; i < uintArray.length; i += batchSize) { const end = i + batchSize < uintArray.length ? i + batchSize : undefined; strings.push(String.fromCharCode(...uintArray.slice(i, end))); } const converted = strings.join(""); - Ken Lyon

8
这也可能会导致一个“Maximum call stack size exceeded”错误:
var items = [];
[].push.apply(items, new Array(1000000)); //Bad

我也是:

items.push(...new Array(1000000)); //Bad

Mozilla Docs得知:
但需注意:使用apply的这种方式有可能会超过JavaScript引擎的参数长度限制。当应用一个具有太多参数(超过数万个参数)的函数时,其结果会因不同引擎而异(JavaScriptCore的硬编码参数限制为65536),因为该限制(实际上包括任何过度堆栈行为的性质)未指定。某些引擎将抛出异常。更加狡猾的是,其他引擎将任意限制传递给应用的函数的参数数量。为了说明后一种情况:如果这样的引擎有四个参数的限制(实际限制当然要高得多),则对于上述示例,就好像传递参数5、6、2和3给apply,而不是整个数组。所以可以尝试:
var items = [];
var newItems = new Array(1000000);
for(var i = 0; i < newItems.length; i++){
  items.push(newItems[i]);
}

这是在将节点插入DOM时非常糟糕的做法,但是你可能是正确的...如果您有足够的节点,在循环后执行el.append(...divs)会导致此错误。 - chovy

8
在我的情况中,点击事件在子元素上进行传播。因此,我必须添加以下内容:

e.stopPropagation()

到点击事件中:
 $(document).on("click", ".remove-discount-button", function (e) {
           e.stopPropagation();
           //some code
        });
 $(document).on("click", ".current-code", function () {
     $('.remove-discount-button').trigger("click");
 });

这是HTML代码:

 <div class="current-code">                                      
      <input type="submit" name="removediscountcouponcode" value="
title="Remove" class="remove-discount-button">
   </div>

6

在我的情况下,是因为我有两个变量使用了相同的名称!


5

在Chrome的开发者工具栏控制台中查看错误详情,这将向你展示调用栈中的函数,并指导你找到导致错误的递归。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接