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

IT技术 javascript callstack
2021-03-12 05:26:06

如果我跑

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

在 Chrome 33 上,我得到

RangeError: Maximum call stack size exceeded

为什么?

5个回答

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

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

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

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

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

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调用here 函数时,它将调用bcbc被调用时,a由于 Javascript 的作用域角色, 的局部变量在那里是不可访问的,但是 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() })上太多次,东西已被推送到调用堆栈。

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

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

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

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

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

这是调用堆栈的说明: 在此处输入图片说明

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

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

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

希望这可以帮助 !

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

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

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

遵循相同的想法,您可以使用ES2015 扩展运算符重写它

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

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

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

Array(1000000).fill().map(() => Math.random()) 也有效
2021-05-03 05:26:06

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

if (debtTypesCounter === debtTypesLength) return;

所以,你可以调整这个想法以适应你的情况。希望这可以帮助?