如果我跑
Array.apply(null, new Array(1000000)).map(Math.random);
在Chrome 33上,我明白了
RangeError: Maximum call stack size exceeded
为什么?
浏览器无法处理那么多论点。请参阅此代码段,例如:
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
电话,它失败了。
所有函数参数必须适合于callstack(至少是每个参数的指针),所以在这里它们对于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
时,它将调用b
和c
。当调用b
和c
时,因为Javascript的作用域角色而无法访问a
的局部变量,但Javascript引擎必须记住局部变量和参数,因此它会将它们推入callstack。假设您正在使用像Narcissus这样的Javascript语言实现JavaScript引擎。
我们将callStack实现为数组:
var callStack = [];
每次调用一个函数时,我们都会将局部变量推送到堆栈中:
callStack.push(currentLocalVaraibles);
一旦函数调用完成(就像在a
中,我们调用了b
,b
已经完成执行,我们必须返回到a
),我们通过弹出堆栈来获取局部变量:
currentLocalVaraibles = callStack.pop();
因此,当在a
中我们想再次调用c
时,推送堆栈中的局部变量。现在如您所知,编译器要有效地定义一些限制。在这里当你做Array.apply(null, new Array(1000000))
时,你的currentLocalVariables
对象将是巨大的,因为它里面会有1000000
变量。由于.apply
将每个给定的数组元素作为参数传递给函数。一旦被推入调用堆栈,这将超过调用堆栈的内存限制,并将抛出该错误。
同样的错误发生在无限递归(function a() { a() }
)上太多次,东西已被推送到调用堆栈。
请注意,我不是编译器工程师,这只是对正在发生的事情的简化表示。它确实比这更复杂。通常推送到callstack的是stack frame,它包含参数,局部变量和函数地址。
您首先需要了解Call Stack。理解调用堆栈还可以让您清楚“函数层次结构和执行顺序”在JavaScript引擎中的工作原理。
调用堆栈主要用于函数调用(调用)。由于调用堆栈是单个的,所以函数执行一次一个地完成,从上到下。这意味着调用堆栈是同步的。当您输入一个函数时,该函数的一个条目被推送到Call堆栈,当您退出该函数时,将从Call Stack中弹出相同的条目。所以,基本上如果一切运行顺畅,那么在开始和结束时,Call Stack将被发现为空。
现在,如果你提供了太多的参数或陷入任何未处理的递归调用。你会遇到的
RangeError:超出最大调用堆栈大小
希望这可以帮助 !
for
的答案是正确的,但如果你真的想使用功能样式避免使用for
语句 - 你可以使用以下代替你的表达式:
Array.from(Array(1000000),()=> Math.random());
Array.from()方法从类似数组或可迭代的对象创建一个新的Array实例。此方法的第二个参数是一个map函数,用于调用数组的每个元素。
遵循相同的想法,你可以使用ES2015 Spread operator重写它:
[... Array(1000000)]。map(()=> Math.random())
在这两个示例中,您可以根据需要获取迭代索引,例如:
[... Array(1000000)]。map((_,i)=> i + Math.random())