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


72

如果我跑步

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

在Chrome 33上,我得到

RangeError: Maximum call stack size exceeded

为什么?


您实际上想做什么?用1000000个随机数填充数组?还是因为其他原因而有其他想法Array.apply
Xotic750

是的,我正在创建一个包含1,000,000个随机数的数组。我正在使用Function.prototype.apply,因为它不会忽略孔。
Fabrizio Giordano 2014年

3
好吧,您已经超出了浏览器以arguments这种方式支持的最大数量。(通常〜65536)。一个for循环可能会更明智。
Xotic750

1
如果您完全确定不使用for循环而真的想使用它,map则可以使用这种慢得多的方法(至少我希望是这样)Object.keys([].concat(Array(10000001).join().split(''))).map(Math.random)
Xotic750 2014年

我写了一个小测试:console.time('object'); var arr = Object.keys([].concat(Array(1000001).join().split(''))).map(Math.random) console.timeEnd('object'); console.time('loop'); var arr = []; var i = 1000000, while(i--){ arr.push(Math.random()); } console.timeEnd('loop'); 对象快2倍。
Fabrizio Giordano 2014年

Answers:


81

浏览器无法处理那么多参数。例如,请参见以下代码片段:

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

30

在这里,它失败Array.apply(null, new Array(1000000))而不是.map呼叫。

所有函数参数必须适合调用堆栈(至少每个参数的指针),因此在此,它们对于调用堆栈而言太多了。

您需要了解什么是调用堆栈

堆栈是一种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被调用时,局部变量a是不可访问的作用域的Javascript角色那里,因为,但JavaScript引擎必须记住的局部变量和参数,所以它会把它们推入调用堆栈。假设您正在使用类似Narcissus的Java语言实现JavaScript引擎。

我们将callStack实现为数组:

var callStack = [];

每当一个函数调用我们将局部变量推入堆栈时:

callStack.push(currentLocalVaraibles);

一旦函数调用完成(如in中a,我们已经调用bb完成执行,必须返回a),我们将通过弹出堆栈来获取局部变量:

currentLocalVaraibles = callStack.pop();

因此,当a我们要c再次调用时,将局部变量压入堆栈。众所周知,高效的编译器会定义一些限制。在这里,当您执行操作时Array.apply(null, new Array(1000000)),您的currentLocalVariables对象将非常庞大,因为其中将包含1000000变量。因为.apply将每个给定的数组元素作为函数的参数传递。一旦推送到调用堆栈,它将超出调用堆栈的内存限制,并且将引发该错误。

在无限递归(function a() { a() })上发生相同的错误,次数过多,已将内容推入调用堆栈。

请注意,我不是编译器工程师,这只是正在发生的事情的简化表示。确实比这更复杂。通常,推送到调用堆栈的内容称为堆栈框架,其中包含参数,局部变量和函数地址。


1
感谢您的解释。我一直在努力理解为什么我在具有讽刺意味的是递归的方法中遇到了同样的异常,但是这种情况只有1级深。事实证明,这不是递归问题。 Array.prototype.push.apply被使用并传递了一个非常大的参数列表。在我看来,该函数的参数列表过大。我确信这是递归问题。再次感谢你!
布兰登

您对JS中的堆栈的解释是错误的。在JS函数中,b()c()可以访问函数a()的局部变量!这称为闭包,这是JS的非常重要的功能。但总的来说,您正在解释正确的堆栈概念
nned

在这个例子中没有闭包,我不想使其更复杂。
Farid Nouri Neshat

10

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

调用堆栈主要用于函数调用(调用)。由于只有一个调用堆栈。因此,所有功能的执行一次从上到下一次被弹出。

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

这是呼叫栈的例证: 在此处输入图片说明

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

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

正如其他人所解释的那样,这是显而易见的。 在此处输入图片说明 在此处输入图片说明

希望这可以帮助 !


3

答案for是正确的,但是如果您确实要使用功能样式避免for语句-您可以使用以下内容代替表达式:

Array.from(Array(1000000),()=> Math.random());

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

遵循相同的想法,您可以使用ES2015 Spread运算符将其重写:

[... Array(1000000)]。map(()=> Math.random())

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

[... Array(1000000)]。map((_,i)=> i + Math.random())


Array(1000000).fill().map(() => Math.random())也有效
tjjfvi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.