JavaScriptの初期バージョンでは、名前付き関数式を使用できませんでした。そのため、再帰関数式を作成できませんでした。
// This snippet will work:
function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
}
[1,2,3,4,5].map(factorial);
// But this snippet will not:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : /* what goes here? */ (n-1)*n;
});
これを回避するために、arguments.callee
が追加されました。
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : arguments.callee(n-1)*n;
});
ただし、これは実際に非常に悪い解決策でした(他の引数、呼び出し先、および呼び出し元の問題と組み合わせて)、インライン化と末尾再帰が一般的なケースで不可能になります(トレースなどを通じて特定のケースで実現できますが、最高のコードでも可能です)他の方法では必要とされないチェックのため、最適ではありません)。その他の主な問題は、再帰呼び出しが異なるthis
値を取得することです。次に例を示します。
var global = this;
var sillyFunction = function (recursed) {
if (!recursed)
return arguments.callee(true);
if (this !== global)
alert("This is: " + this);
else
alert("This is the global");
}
sillyFunction();
とにかく、EcmaScript 3は名前付き関数式を許可することでこれらの問題を解決しました。
[1,2,3,4,5].map(function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
});
これには多くの利点があります:
おっと、
他のすべてのものに加えて、問題がについてarguments.callee.caller
であるか、より具体的にであることがわかりましたFunction.caller
。
いつでも、スタック上の任意の関数の最も深い呼び出し元を見つけることができます。前述のように、呼び出しスタックを見ると、1つの大きな影響があります。それは、多数の最適化を不可能にするか、はるかに困難にします。
例えば。関数f
が未知の関数を呼び出さないことを保証できない場合、インライン化することはできませんf
。基本的に、それは、自明のことではなかった可能性がある呼び出しサイトが多数のガードを蓄積することを意味します。
function f(a, b, c, d, e) { return a ? b * c : d * e; }
jsインタープリターが、呼び出しが行われた時点で提供されたすべての引数が数値であることを保証できない場合は、インラインコードの前にすべての引数のチェックを挿入するか、関数をインライン化できません。
さて、この特定のケースでは、スマートインタープリターは、チェックをより最適なものに再配置し、使用されない値をチェックできないようにする必要があります。ただし、多くの場合それは不可能であり、インライン化は不可能になります。