“RangeError:超出最大调用堆栈大小”为什么?

时间:2014-03-02 04:15:21

标签: javascript callstack

如果我跑

Array.apply(null, new Array(1000000)).map(Math.random);

在Chrome 33上,我得到了

  

RangeError: Maximum call stack size exceeded

为什么?

5 个答案:

答案 0 :(得分:70)

浏览器无法处理那么多参数。请参阅此代码段:

alert.apply(window, new Array(1000000000));

这会产生RangeError: Maximum call stack size exceeded,这与您的问题相同。

要解决这个问题,请执行:

var arr = [];
for(var i = 0; i < 1000000; i++){
    arr.push(Math.random());
}

答案 1 :(得分:28)

此处它在Array.apply(null, new Array(1000000))处失败,而不是.map来电。

所有函数参数必须适合callstack(至少是每个参数的指针),所以这里的参数太多了。

您需要了解call stack是什么。

Stack是一个LIFO数据结构,就像一个只支持push和pop方法的数组。

让我通过一个简单的例子解释它是如何工作的:

function a(var1, var2) {
    var3 = 3;
    b(5, 6);
    c(var1, var2);
}
function b(var5, var6) {
    c(7, 8);
}
function c(var7, var8) {
}

当调用此功能a时,它会调用bc。调用bc时,由于Javascript的作用域角色,a的局部变量无法访问,但Javascript引擎必须记住局部变量和参数,因此它将将它们推入callstack。假设您正在使用Javascript语言Narcissus实现JavaScript引擎。

我们将callStack实现为数组:

var callStack = [];

每次调用一个函数时,我们都会将局部变量推送到堆栈中:

callStack.push(currentLocalVaraibles);

完成函数调用后(例如在a中,我们调用了bb已完成执行,我们必须返回a),我们回来了弹出堆栈的局部变量:

currentLocalVaraibles = callStack.pop();

因此,当在a中我们想再次调用c时,推送堆栈中的局部变量。现在如您所知,编译器要有效地定义一些限制。当您执行Array.apply(null, new Array(1000000))时,您的currentLocalVariables对象将会非常庞大​​,因为它内部会有1000000个变量。由于.apply将每个给定的数组元素作为参数传递给函数。一旦被推入调用堆栈,这将超过调用堆栈的内存限制,并将抛出该错误。

在无限递归(function a() { a() })上发生相同的错误太多次,东西已被推送到调用堆栈。

请注意,我不是编译器工程师,这只是对正在发生的事情的简化表示。它确实比这更复杂。通常推送到callstack的是stack frame,它包含参数,局部变量和函数地址。

答案 2 :(得分:2)

您首先需要了解Call Stack。了解调用堆栈还将使您更清楚地了解“函数层次结构和执行顺序”在JavaScript Engine中的工作方式。

调用堆栈主要用于函数调用(调用)。由于调用堆栈是单个的,因此函数的执行从上到下一次完成。这意味着调用堆栈是同步的。当您输入一个函数时,该函数的条目将被推送到调用堆栈上,而当您退出该函数时,该条目将从调用堆栈中弹出。因此,基本上,如果一切运行顺利,那么在开始和结束时,“调用堆栈”将被发现为空。

这是呼叫堆栈的图示: enter image description here

现在,如果您提供了太多参数或陷入了任何未处理的递归调用中。您会遇到

  

RangeError:超出了最大调用堆栈大小

这很明显,正如其他人所解释的那样。 enter image description here enter image description here

希望这会有所帮助!

答案 3 :(得分:1)

for的答案是正确的,但如果你真的想使用功能样式避免for语句 - 你可以使用以下代替你的表达式:

  

Array.from(Array(1000000),()=&gt; Math.random());

Array.from()方法从类似数组或可迭代的对象创建一个新的Array实例。这个方法的第二个参数是一个map函数,用于调用数组的每个元素。

遵循相同的想法,您可以使用ES2015 Spread operator重写它:

  

[...数组(1000000)]。map(()=&gt; Math.random())

在这两个示例中,您可以根据需要获取迭代索引,例如:

  

[...数组(1000000)]。map((_,i)=&gt; i + Math.random())

答案 4 :(得分:1)

根据我的经验,此错误是由永不终止的递归函数引起的。所以我设置了递归必须停止执行并返回(中断)的条件。 所以通过添加下面的代码行,我能够摆脱这个错误。

if (debtTypesCounter === debtTypesLength) return;

因此,您可以调整这个想法以适应您的情况。希望这有帮助吗?