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

Posted

技术标签:

【中文标题】“RangeError:超出最大调用堆栈大小”为啥?【英文标题】:"RangeError: Maximum call stack size exceeded" Why?“RangeError:超出最大调用堆栈大小”为什么? 【发布时间】:2014-04-03 03:27:10 【问题描述】:

如果我跑了

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

在 Chrome 33 上,我得到了

RangeError: Maximum call stack size exceeded

为什么?

【问题讨论】:

你到底想做什么?用 1000000 个随机数填充一个数组?还是因为Array.apply,您有其他想法? 是的,我正在创建一个包含 1,000,000 个随机数的数组。我正在使用 Function.prototype.apply 因为它不会忽略漏洞。 嗯,你已经超过了浏览器支持的arguments 的最大数量。 (通常〜65536)。 for 循环可能更明智。 如果你绝对决定不使用for 循环并且真的想使用map 那么你可以使用这个慢得多的方法(至少我希望它是)Object.keys([].concat(Array(10000001).join().split(''))).map(Math.random) 我写了一个小测试: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'); Object is 2x time faster. 【参考方案1】:

浏览器无法处理那么多参数。例如看这个 sn-p:

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

【讨论】:

【参考方案2】:

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

所有函数的参数都必须适合调用堆栈(至少每个参数的指针),所以在这种情况下它们对于调用堆栈来说参数太多了。

您需要了解什么是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引擎必须记住局部变量和参数,所以它将它们推入调用堆栈。假设您正在使用像 Narcissus 这样的 Javascript 语言实现一个 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() ) 上,因为太多次,东西已被推送到调用堆栈。

请注意,我不是编译器工程师,这只是对正在发生的事情的简化表示。它真的比这更复杂。一般推送到调用栈的称为stack frame,其中包含参数、局部变量和函数地址。

【讨论】:

感谢您对此的解释。我一直在努力理解为什么我在一个具有讽刺意味的是递归的方法中得到同样的异常,但它只发生在 1 级深度。事实证明,这不是递归问题。 Array.prototype.push.apply 被使用并传递了一个非常大的参数列表。我从来没有想过该函数的参数列表太大。我确信这是一个递归问题。再次感谢您! 您对 JS 中堆栈的解释有一半是错误的。在 JS 函数中 b()c() 将可以访问函数 a() 的局部变量!这称为闭包,这是 JS 非常重要的特性。但总的来说,您正在以正确的方式解释堆栈的概念 本例中没有闭包,我不想让它变得更复杂。【参考方案3】:

for 的答案是正确的,但如果您真的想使用避免for 语句的函数式风格 - 您可以使用以下表达式来代替您的表达式:

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

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

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

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

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

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

【讨论】:

Array(1000000).fill().map(() =&gt; Math.random()) 也有效【参考方案4】:

您首先需要了解调用堆栈。了解调用堆栈还将让您清楚地了解“函数层次结构和执行顺序”在 JavaScript 引擎中是如何工作的。

调用栈主要用于函数调用(call)。因为只有一个调用栈。因此,所有函数的执行都会从上到下一次推送和弹出一个。

这意味着调用堆栈是同步的。当您输入一个函数时,该函数的一个条目会被压入调用堆栈,当您退出该函数时,该条目会从调用堆栈中弹出。所以,基本上如果一切顺利,那么在开始和结束的时候,Call Stack 都会发现是空的。

下面是调用栈的说明:

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

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

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

希望这会有所帮助!

【讨论】:

【参考方案5】:

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

if (debtTypesCounter === debtTypesLength) return;

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

【讨论】:

以上是关于“RangeError:超出最大调用堆栈大小”为啥?的主要内容,如果未能解决你的问题,请参考以下文章

Nowjs:[RangeError:超出最大调用堆栈大小]

猫鼬:UnhandledPromiseRejectionWarning:RangeError:超出最大调用堆栈大小

Node.js + mongoose [RangeError: 超出最大调用堆栈大小]

VueJS“RangeError:超出最大调用堆栈大小”

Rails:ExecJS :: ProgramError:RangeError:超出最大调用堆栈大小

Angular 5 表单组件导致错误:“RangeError:超出最大调用堆栈大小”