Warum wurde die Eigenschaft rules.callee.caller in JavaScript nicht mehr unterstützt?

214

Warum wurde die arguments.callee.callerEigenschaft in JavaScript nicht mehr unterstützt?

Es wurde hinzugefügt und dann in JavaScript veraltet, aber von ECMAScript insgesamt weggelassen. Einige Browser (Mozilla, IE) haben dies immer unterstützt und haben keine Pläne auf der Karte, die Unterstützung zu entfernen. Andere (Safari, Opera) haben Unterstützung dafür übernommen, aber die Unterstützung in älteren Browsern ist unzuverlässig.

Gibt es einen guten Grund, diese wertvolle Funktionalität in die Schwebe zu bringen?

(Oder gibt es alternativ eine bessere Möglichkeit, die aufrufende Funktion in den Griff zu bekommen?)

pcorcoran
quelle
2
Es wird von anderen Browsern unterstützt, da jede Funktion, die weit verbreitet ist, zu einem Kompatibilitätsfehler für andere Browser wird. Wenn eine Site eine Funktion verwendet, die nur in einem Browser vorhanden ist, ist die Site in allen anderen defekt, und Benutzer denken normalerweise, dass der Browser defekt ist.
olliej
4
(Fast alle Browser haben dies zu der einen oder anderen Zeit getan, z. B. stammt diese Funktion (und JS selbst) von Netscape, XHR aus dem Internet Explorer, Canvas in Safari usw. Einige davon sind nützlich und werden von den anderen Browsern übernommen im Laufe der Zeit (js, canvas, xhr sind alle Beispiele), sind einige (.callee) nicht.
olliej
@olliej Ihr Kommentar zur Unterstützung, weil es verwendet wird und nicht, weil es ein Standard ist (oder sogar obwohl es im Standard veraltet ist), ist sehr wahr! Aus diesem Grund habe ich angefangen, die Standards meistens zu ignorieren, wenn ich das Gefühl habe, dass sie mir nicht helfen. Wir als Entwickler können die Richtung von Standards bestimmen, indem wir verwenden, was funktioniert und nicht, was die Spezifikation vorschreibt. So kamen <b>und kamen wir <i>zurück (ja, diese waren an einem Punkt veraltet).
Stijn de Witt

Antworten:

252

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.calleewurde 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 thisWert 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 fkeine 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.

olliej
quelle
12
Wollen Sie damit sagen, dass es nur deshalb benachteiligt ist, weil es schwer zu optimieren ist? Das ist irgendwie albern.
Thomas Eding
11
Nein, ich habe eine Reihe von Gründen aufgeführt, die es zusätzlich schwierig machen, zu optimieren (obwohl in der allgemeinen Geschichte gezeigt wurde, dass Dinge, die schwer zu optimieren sind, auch eine Semantik haben, der die Leute nur schwer folgen können)
olliej
17
Das dieses Argument ist ein bisschen unecht, kann sein Wert durch den Aufruf gesetzt werden , wenn es wichtig ist . Normalerweise wird es nicht verwendet (zumindest hatte ich in rekursiven Funktionen noch nie ein Problem damit). Das Aufrufen der Funktion beim Namen hat die gleichen Probleme, thisdaher denke ich, dass es für die Frage, ob Angerufene gut oder schlecht sind, irrelevant ist. Außerdem sind Angerufene und Anrufer nur im strengen Modus "veraltet" (ECMAscript ed 5, Dec 2009), aber ich denke, das war 2008 nicht bekannt, als olliej gepostet hat.
RobG
8
) Ich sehe die Logik immer noch nicht. In jeder Sprache mit erstklassigen Funktionen ist es ein klarer Wert, einen Funktionskörper definieren zu können, der sich auf sich selbst beziehen kann, ohne es wissen zu müssen
Mark Reed
8
RobG hat darauf hingewiesen, aber ich glaube nicht, dass alles so klar war: Wenn Sie eine benannte Funktion erneut verwenden, bleibt nur der Wert von thisif thisals globaler Bereich erhalten. In allen anderen Fällen wird der Wert this wird nach dem ersten rekursiven Aufruf ändern, so dass ich die Teile Ihrer Antwort denken , auf die Erhaltung anspielend von thisnicht wirklich gültig sind.
JLRishe
89

arguments.callee.callerist nicht veraltet, obwohl es die Eigenschaft nutzt . ( gibt Ihnen nur einen Verweis auf die aktuelle Funktion)Function.callerarguments.callee

  • Function.callerObwohl gemäß ECMA3 nicht standardisiert, ist es in allen gängigen Hauptbrowsern implementiert .
  • arguments.caller wird zugunsten von abgelehnt und ist in einigen aktuellen Hauptbrowsern (z. B. Firefox 3) nicht implementiert.Function.caller

Die Situation ist also nicht ideal, aber wenn Sie über alle gängigen Browser auf die aufrufende Funktion in Javascript zugreifen möchten, können Sie die Eigenschaft verwenden, auf die entweder direkt über eine benannte Funktionsreferenz oder über die Eigenschaft innerhalb einer anonymen Funktion zugegriffen wird .Function.callerarguments.callee

James Wheare
quelle
5
Dies ist die beste Erklärung dafür, was veraltet ist und was nicht, sehr nützlich. Ein gutes Beispiel dafür, was Function.caller nicht kann (Stapelverfolgung rekursiver Funktionen abrufen), finden Sie unter developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Juan Mendes,
1
Ist arguments.calleejedoch im strengen Modus verboten. Es hat mich auch traurig gemacht, aber es ist besser, es nicht mehr zu benutzen.
Gras Double
1
Der Hyperlink argument.callee, den Sie zu MDN haben, besagt, dass er im strengen Modus entfernt wird. Ist das nicht dasselbe wie veraltet?
Styfle
1
Beachten Sie, dass dies arguments.callee.callerim strengen ES5-Modus veraltet ist: "Eine weitere Funktion, die nicht mehr unterstützt wurde, war argument.callee.caller oder genauer Function.caller." ( Quelle )
Uhr
29

Es ist besser, benannte Funktionen als Argumente zu verwenden.

 function foo () {
     ... foo() ...
 }

ist besser als

 function () {
     ... arguments.callee() ...
 }

Die benannte Funktion hat über die Caller- Eigenschaft Zugriff auf ihren Aufrufer :

 function foo () {
     alert(foo.caller);
 }

das ist besser als

 function foo () {
     alert(arguments.callee.caller);
 }

Die Abschreibung ist auf die aktuellen ECMAScript- Gestaltungsprinzipien zurückzuführen .

Zach
quelle
2
Können Sie beschreiben, warum die Verwendung der genannten Funktion besser ist? Müssen Sie in einer anonymen Funktion niemals Angerufene verwenden?
AnthonyWJones
27
Wenn Sie Angerufene in einer anonymen Funktion verwenden, haben Sie eine Funktion, die nicht anonym sein sollte.
Prestaul
3
Manchmal ist das Debuggen am einfachsten mit .caller (). In solchen Fällen helfen benannte Funktionen nicht - Sie versuchen herauszufinden, welche Funktion den Aufruf ausführt.
SamGoody
6
Besser definieren. Zum Beispiel hat IE6-8 Funktions-Macken benannt, während argument.callee funktioniert.
cmc
1
Abgesehen von den Macken des IE6-8 wird auch der Code eng gekoppelt. Wenn die Namen von Objekten und / oder Funktionen fest codiert sind, gibt es, wie in ardsasd und rsk82 erwähnt, große Refactoring-Gefahren, die nur mit zunehmender Größe der Codebasis zunehmen. Unit-Tests sind eine Verteidigungslinie, und ich benutze sie, aber sie sind immer noch keine Antwort, die mich persönlich in Bezug auf dieses Hardcodierungsproblem wirklich zufriedenstellt.
Jasmine Hegman
0

Nur eine Erweiterung. Der Wert von "this" ändert sich während der Rekursion. Im folgenden (modifizierten) Beispiel erhält Fakultät das Objekt {foo: true}.

[1,2,3,4,5].map(function factorial(n) {
  console.log(this);
  return (!(n>1))? 1 : factorial(n-1)*n;
},     {foo:true}     );

Beim ersten Aufruf von Fakultät wird das Objekt abgerufen, dies gilt jedoch nicht für rekursive Aufrufe.

FERcsI
quelle
1
Weil du es falsch machst. Wenn thisbeibehalten werden muss, schreiben Sie, was factorial.call(this, n-1)ich beim Schreiben von rekursivem Code tatsächlich festgestellt habe, dass es normalerweise keinen thisgibt oder thisauf einen Knoten in einem Baum verweist, und tatsächlich ist es gut, dass er sich ändert.
Stijn de Witt