Frühere Versionen von JavaScript erlaubten keine benannten Funktionsausdrücke, und aus diesem Grund konnten wir keinen rekursiven Funktionsausdruck erstellen:
// 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;
});
Um dies zu umgehen, arguments.callee
wurde hinzugefügt, damit wir Folgendes tun können:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : arguments.callee(n-1)*n;
});
Dies war jedoch tatsächlich eine wirklich schlechte Lösung, da dies (in Verbindung mit anderen Argumenten, Angerufenen und Anruferproblemen) das Inlining und die Schwanzrekursion im allgemeinen Fall unmöglich macht (Sie können dies in ausgewählten Fällen durch Nachverfolgung usw. erreichen, aber auch durch den besten Code ist aufgrund von Prüfungen, die sonst nicht notwendig wären, suboptimal). Das andere Hauptproblem ist, dass der rekursive Aufruf einen anderen this
Wert erhält, zum Beispiel:
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();
Wie auch immer, EcmaScript 3 hat diese Probleme gelöst, indem benannte Funktionsausdrücke zugelassen wurden, z.
[1,2,3,4,5].map(function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
});
Dies hat zahlreiche Vorteile:
Die Funktion kann wie jede andere in Ihrem Code aufgerufen werden.
Der Namespace wird nicht verschmutzt.
Der Wert von this
ändert sich nicht.
Es ist performanter (der Zugriff auf das Argumentobjekt ist teuer).
Hoppla,
Ich habe gerade festgestellt, dass es neben allem anderen um die Frage ging arguments.callee.caller
, genauer gesagt Function.caller
.
Zu jedem Zeitpunkt können Sie den tiefsten Aufrufer einer Funktion auf dem Stapel finden, und wie ich oben sagte, hat das Betrachten des Aufrufstapels einen einzigen Haupteffekt: Es macht eine große Anzahl von Optimierungen unmöglich oder viel schwieriger.
Z.B. Wenn wir nicht garantieren können, dass eine Funktion f
keine unbekannte Funktion aufruft, kann nicht inline geschaltet werden f
. Grundsätzlich bedeutet dies, dass jede Anrufstelle, die möglicherweise trivial inlinierbar war, eine große Anzahl von Wachen ansammelt. Nehmen Sie:
function f(a, b, c, d, e) { return a ? b * c : d * e; }
Wenn der js-Interpreter zum Zeitpunkt des Aufrufs nicht garantieren kann, dass alle angegebenen Argumente Zahlen sind, muss er entweder vor dem Inline-Code Überprüfungen für alle Argumente einfügen oder die Funktion nicht einbinden.
In diesem speziellen Fall sollte ein intelligenter Interpreter in der Lage sein, die Prüfungen neu anzuordnen, um sie optimaler zu gestalten, und keine Werte zu prüfen, die nicht verwendet würden. In vielen Fällen ist dies jedoch einfach nicht möglich und daher wird es unmöglich, Inline zu erstellen.