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;
});
ただし、これは実際には本当に悪い解決策でした。これは(他の引数、呼び出し先、および呼び出し元の問題と組み合わせて)、一般的なケースではインライン化と末尾再帰を不可能にするためです(トレースなどを介して特定のケースでそれを達成できますが、最高のコードでも他の方法では必要ないチェックのため、最適ではありません)。もう1つの大きな問題は、再帰呼び出しが異なる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インタープリターは、呼び出された時点で提供されたすべての引数が数値であることを保証できない場合、インライン化されたコードの前にすべての引数のチェックを挿入するか、関数をインライン化できません。
この特定のケースでは、スマートインタープリターは、チェックをより最適になるように再配置し、使用されない値をチェックしないようにする必要があります。ただし、多くの場合、それは不可能であるため、インライン化が不可能になります。