走ったら
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());
}
ここでは、.map
呼び出しではなく、Array.apply(null, new Array(1000000))
で失敗します。
すべての関数の引数は、呼び出しスタック(少なくとも各引数のポインター)に収まらなければならないため、これでは呼び出しスタックに対して引数が多すぎます。
呼び出しスタック とは何かを理解する必要があります。
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
が呼び出されると、a
のローカル変数はJavascriptのスコープロールのためにそこにアクセスできませんが、Javascriptエンジンはローカル変数と引数を覚えて、コールスタックにプッシュする必要があります。 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 と呼ばれ、引数、ローカル変数、関数アドレスが含まれます。
最初に呼び出しスタックを理解する必要があります。コールスタックを理解すると、JavaScriptエンジンで「関数の階層と実行順序」がどのように機能するかがわかります。
呼び出しスタックは、主に関数呼び出し(呼び出し)に使用されます。呼び出しスタックは単一であるため、関数の実行は、上から下に一度に1つずつ行われます。これは、呼び出しスタックが同期的であることを意味します。関数を入力すると、その関数のエントリが呼び出しスタックにプッシュされ、関数を終了すると、その同じエントリが呼び出しスタックからポップされます。そのため、基本的にすべてがスムーズに実行されている場合、最初と最後で呼び出しスタックは空になります。
引数の数が多すぎる場合、または未処理の再帰呼び出し内でキャッチされた場合。出会うでしょう
RangeError:最大呼び出しスタックサイズを超えました
お役に立てれば !
for
での答えは正しいですが、for
ステートメントを避けて機能スタイルを本当に使用したい場合は、式の代わりに以下を使用できます。
Array.from(Array(1000000)、()=> Math.random());
Array.from()メソッドは、配列のようなオブジェクトまたは反復可能なオブジェクトから新しいArrayインスタンスを作成します。このメソッドの2番目の引数は、配列のすべての要素を呼び出すマップ関数です。
同じ考えに従って ES2015 Spread operator を使用して書き換えることができます。
[... Array(1000000)]。map(()=> Math.random())
どちらの例でも、必要に応じて反復のインデックスを取得できます。たとえば、次のとおりです。
[... Array(1000000)]。map((_、i)=> i + Math.random())