benannte Methoden
MethodenTester
- javascript
Ich bin heute durch Rumprobieren auf ein Verhalten von Javascript gestoßen, dass ich nicht ganz verstehe.
ClassA hat zwei Methoden, von denen eine benannt ist, die andere nicht.
Beide tun das Gleiche, sie rufen foo(). Es wird jedoch einemal die Object-methode foo() aufgerufen, die "2222222" ausgibt und einmal die funktion foo(), die "1111111" ausgbit.
Wie kommt es, dass die Object-methode foo aufgerufen wird?
Was passiert genau, wenn ich einer methode einen Namen verpasse (siehe methodX)?
function foo(){
alert("1111111111");
}
Object.prototype.foo = function(){
alert("22222222222");
}
function ClassA(){
this.methodX = function methodX(){
foo();
}
this.methodY = function(){
foo();
}
}
var c = new ClassA();
c.methodX();
c.methodY();
Wie kommt es, dass die Object-methode foo aufgerufen wird?
Das passiert nur im FF (nicht im IE oder Opera).
Was passiert genau, wenn ich einer methode einen Namen verpasse (siehe methodX)?
Eigentlich nichts.
Aber, da es nur FF macht, vermute ich hier einen Bug. FF scheint in dem Fall, die Suche nach dem Kontextobjekt anders anzugehen
Normalerweise: window -> Object
hier: Object -> window
Warum kann ich mir nicht erklären, kein anderer Browser macht das so.
Struppi.
Aber, da es nur FF macht, vermute ich hier einen Bug. FF scheint in dem Fall, die Suche nach dem Kontextobjekt anders anzugehen
Normalerweise: window -> Object
hier: Object -> windowWarum kann ich mir nicht erklären, kein anderer Browser macht das so.
Struppi.
Ok, danke für die Antwort. Ich glaube du hast recht.
Wie kommt es, dass die Object-methode foo aufgerufen wird?
Das passiert nur im FF (nicht im IE oder Opera).
Dann machen IE und Opera was falsch.
Was passiert genau, wenn ich einer methode einen Namen verpasse (siehe methodX)?
Eigentlich nichts.
Doch, es wird ein neues Objekt erstellt und der Scope-Chain vorangestellt. Und was befindet sich in diesem Objekt? Bingo, über die Prototypen-Kette findet er dort die Funktion Object.prototype.foo!
Aber, da es nur FF macht, vermute ich hier einen Bug. FF scheint in dem Fall, die Suche nach dem Kontextobjekt anders anzugehen
Normalerweise: window -> Object
hier: Object -> window
Was du hier meinst, verstehe ich nicht. Das per Konstruktor erzeugte Objekt ist doch etwas anderes als das Kontextobjekt.
Warum kann ich mir nicht erklären, kein anderer Browser macht das so.
Hier die Erklärung:
Ecma-262, "13 Function Definition" (Seite 71) sagt zur Auswertung von function bla(){...}
als Function Expression:
- Create a new object as if by the expression new Object().
- Add Result(1) to the front of the scope chain.
- Create a new Function object as specified in 13.2 with parameters specified by FormalParameterListopt
and body specified by FunctionBody. Pass in the scope chain of the running execution context as the
Scope.- Create a property in the object Result(1). The property's name is Identifier, value is Result(3), and
attributes are { DontDelete, ReadOnly }.- Remove Result(1) from the front of the scope chain.
- Return Result(3).
1. und 2. sorgen dafür, dass Object.prototype.foo in der Scope Chain auftaucht. Firefox verhält sich also völlig konform zu Ecma-262.
Übrigens ein schönes Beispiel dafür, dass man Object.prototype besser in Ruhe lassen sollte. Oder wirklich Sorgfalt walten lassen muss.
Hallo,
Aber, da es nur FF macht, vermute ich hier einen Bug. FF scheint in dem Fall, die Suche nach dem Kontextobjekt anders anzugehen
Normalerweise: window -> Object
hier: Object -> window
Ich guck bei so etwas immer gerne in der ECMAScript-Spezifikation nach, so schwierig und tröge zu lesen sie auch ist. ;-) Aber nur so kriegt man raus, wie es sein soll und wer davon abweicht und ob das wirklich zu verurteilen ist.
Erstmal, um klar zu werden, was da überhaupt passiert, das Ganze ist ein sogenannter Zuweisungsausdruck. Links vom Gleicheheitszeichen steht ein Ausdruck, rechts vom Gleichzeitszeichen steht ein Ausdruck, eine function expression, ein Funktionsausdruck, keine „benannte Methode“:
this.foo = function foo ( ) { bar(); };
[----Funktionsausdruck----]
In der ECMAScript-Spezifikation ist die Syntax für Funktionsausdrücke so definiert:
FunctionExpression :
"function" 'Identifier'? "(" FormalParameterList? ")" "{" FunctionBody "}"
Ich habe hier die sogenannten Terminalsymbole, d.h. „richtige“ ECMAScript-Syntax, in doppelte Anführungszollzeichen gesetzt, selbst zu vergebende Namen in einfache Anführungsapostrophen gesetzt, Bestandteile, deren Syntax woanders definiert sind so gelassen, optionale Elemente mit einem folgendem Fragezeichen ausgestattet und Leerraum Leerraum gelassen. Die ECMAScript-Spezifikation hat mehr typographische Elemente zu Verfügung und macht das durchaus hübscher. Man kann es auch so lesen: „Ein Funktionsausdruck wird definiert durch die Zeichenfolge ‚function‘, einem optionalen, selbst zu vergebenden Identifizierer, einer öffnenden Klammer, einer Parameterliste, deren Syntax woanders definiert ist, einer schließenden Klammer, einer öffnenden geschweiften Klammer, dem die Funktion ausmachenden Code und einer schließenden geschweiften Klammer.“ Ich ziehe dann doch speziellere kleine Minisprachen für so etwas vor.
Wird nun so ein Funktionsausdruck ausgewertet, geschieht das je nach Vorhandensein des zusätzlichen Identifizierers unterschiedlich. Zuerst den Normalfall, ohne Identifizierer:
The production FunctionExpression : function ( FormalParameterList? ){ FunctionBody }
is evaluated as follows:
1. Create a new Function object as specified in 13.2 with parameters specified by
FormalParameterList? and body specified by FunctionBody. Pass in the scope chain of
the running execution context as the Scope.
2. Return Result(1).
Übersetzt:
„Erstelle ein neues Funktionsobjekt mit den optionalen Parametern, dem entsprechenden Code und nutze zur Namensauflösung innerhalb des Funktionsobjektes die um Zeitpunkt der Funktionsdefinition gültige Liste der Objekte, in denen nach Namen gesucht werden. Gib dieses Funktionsobjekt zurück.“
Ist jedoch ein Identifizierer für die Funktion vorhanden, gilt eine andere Definition zu Auswertung:
The production FunctionExpression : function Identifier ( FormalParameterListopt ) { FunctionBody }
is evaluated as follows:
1. Create a new object as if by the expression new Object().
2. Add Result(1) to the front of the scope chain.
3. Create a new Function object as specified in 13.2 with parameters specified by
FormalParameterListopt and body specified by FunctionBody. Pass in the scope chain of
the running execution context as the Scope.
4. Create a property in the object Result(1). The property's name is Identifier, value
is Result(3), and attributes are { DontDelete, ReadOnly }.
5. Remove Result(1) from the front of the scope chain.
6. Return Result(3).
Übersetzt:
1. Erstelle ein neues, simples Objekt.
2. Setze dieses Objekt an die Spitze der Liste der Objekte, in denen nach Namen
gesucht werden soll.
3. Erstelle ein neues Funktionsobjekt .. yadayadayada.
4. In diesem geheimnisvollen Objekt erstelle eine einzige nicht lösch- und nicht
überschreibbare Eigenschaft mit dem Namen gleich dem Identifizierer, die auf das
Funktionsobjekt zeigt.
5. Mach die Namensauflösungsliste wieder auf normal; wir wollen ja niemanden stören
6. Gib das Funktionsobjekt zurück.
In solchen Funktionsobjekten geschieht die Namensauflösung also anders. In der Liste von Objekten in den nach Namen gesucht werden soll, findet sich immer an erster Stelle ein Objekt, das den Namen der Funktion beinhaltet und damit auf die Funktion selber zeigt. Oder noch simpler: In benannten Funktionen zeigt der Funktionsname immer auf die Funktion selber – und man kann das nicht ändern.
Warum man das macht? Um rekursiv programmieren zu können, braucht man immer eine Referenz auf die Funktion selber; in ECMAScript hat man sich vermutlich eben wegen der ziemlich starken Dynamik der Namensauflösung für diese Lösung entschieden. Eine andere denkbare Lösung wäre z.B. ein spezielles Schlüsselwort wie „func“ gewesen, das immer auf die Funktion selber zeigt, analog zu „this“, dass immer auf das Objekt zeigt, in dessen Kontext die Funktion ausgeführt wird. Aber ist halt nicht. Und eigentlich ist es doch auch eine ganz vernünftige Lösung.
Was passiert nun beim Methodentester?
~~~javascript
function ClassA ( ) {
this.methodX = function methodX () {
foo();
};
}
Das generierte Funktionsobjekt methodX hat nun dank der obigen Definition eine eigene scope chain. Diese Liste von Objekten, in denen nach Namen gesucht wird, sieht dann vereinfacht so aus:
`[Obj, <scope von ClassA> window]`{:.language-javascript}
„Obj“ ist das neue anonyme Objekt; in JSON-ähnlicher Notation sieht es so aus: { "methodX" : <funktionsObjekt> }. Wenn nun ein Name in der Funktion gesucht wird, wird in der Liste von Objekten nach diesem Namen erst in Obj gesucht, dann im Geltungsbereich von ClassA, als letztes in window. Es wird aber ganz klassisch nach Namen in Objekten gesucht. Jedes, aber auch wirklich jedes Objekt ist prototypisch mit der Eigenschaft „foo“ erweitert worden, d.h. wenn man in jedem Objekt nach „foo“ sucht, findet man auch „foo“. Und das gilt auch für unser anonymes Objekt obj; es wird innerhalb methodX immer object.prototype.foo gefunden.
~~~javascript
function ClassA ( ) {
this.methodY = function () {
foo();
};
}
Hier dagegen sieht die scope chain so aus: [<scope von ClassA], window]
. Im Geltungsbereich von ClassA findet sich kein „foo“, also wird in window gesucht. Und im window-Objekt wird window.foo vor window.prototype.foo gefunden, weil so nun mal die Findung von Eigenschaften in prototypisch erweiterten Objekten funktioniert, erst wird im Objekt gesucht und dann im Prototyp, dann in dessen Prototyp, etc. Wie so oft schießt man sich in den eigenen Fuß, wenn man object.prototype verändert.
Warum kann ich mir nicht erklären, kein anderer Browser macht das so.
Firefox macht es mal wieder als einziger richtig. ;)
Tim
Ich guck bei so etwas immer gerne in der ECMAScript-Spezifikation nach, so schwierig und tröge zu lesen sie auch ist. ;-) Aber nur so kriegt man raus, wie es sein soll und wer davon abweicht und ob das wirklich zu verurteilen ist.
Genau davor und aus diesem Grund hatte ich mich gedrückt, ich weiß ja, dass es hier Leute gibt die sowas gerne lesen ;-)
.... und dann noch übersetzen und Punkt für Punkt erklären, vielen Dank auch von mir.
Struppi.
Warum man das macht? Um rekursiv programmieren zu können, braucht man immer eine Referenz auf die Funktion selber; in ECMAScript hat man sich vermutlich eben wegen der ziemlich starken Dynamik der Namensauflösung für diese Lösung entschieden. Eine andere denkbare Lösung wäre z.B.
... arguments.callee.
Und eigentlich ist es doch auch eine ganz vernünftige Lösung.
Finde ich nicht. Ich halte die Sonderregel für überflüssig und verwirrend. Wieso diese Unterscheidung?
Übrigens erzeugt eine Function Expression mit Namen im IE eine Variable im aktuellen Scope. Daher sollte man das in der normalen browserübergreifenden JavaScript-Entwicklung tunlichst lassen.
(function () {
alert(function funcExpr () {});
alert(typeof funcExpr);
})();
Mathias
Hallo Mathias,
... arguments.callee.
Das arguments-Objekt wird immer dann neu erstellt, wenn man in eine Funktion reinkommt und callee drangepropft. Heisst: Immer, aber auch immer, wenn das Funktionsobjekt ausgeführt wird. Der Identifier dagegen steht beim ersten Auswerten der Function Expression fest, braucht in einer tiefen Rekursion nicht immer noch zusätzlich Objekte. arguments ist auch nicht wirklich berechenbar, weil eben sehr flexibel. Zusätzlich ist callee auch noch nur { DontEnum } und nicht noch { DontDelete, ReadOnly }, damit kann also rumgewurschtelt werden. Solch eine Unberechenbarkeit etwas steht etwas im Weg, wenn der Interpreter, die VM, der Compiler Optimierungen betreiben wollen, z.B. Tail Call Optimierung, die gerade bei Rekursion es schaffen kann, schon vor der Ausführung die Absicht der vielen den Stack verstopfenden offene Funktions-Ausführungs-Objekte in Schleifenform webzuoptimieren.
Finde ich nicht. Ich halte die Sonderregel für überflüssig und verwirrend. Wieso diese Unterscheidung?
Wohl historisch gewachsen, Named Function Expressions kamen erst in ES 3 dazu. Brendan Eich hat sie meiner Erinnerung nach irgendwo mal als Karotte bezeichnet, mit denen man Programmierer locken wollte. Ich verfolge das nur am Range, aber im ES-Land scheint man durchaus unglücklich mit arguments.callee zu sein. Im Strict-Modus von ES 3.1 soll callee nicht nutzbar sein; im alten ES-4-Vorschlag gab es ein eigenes Keyword mit der ziemlich abstrusen Syntax „this function“ – ja, inklusive Leerzeichen. Was im Projekt der Großen Harmonie damit wird: keine Ahnung.
Übrigens erzeugt eine Function Expression mit Namen im IE eine Variable im aktuellen Scope.
Ja, das wird als Function Declaration verarbeitet. Ist das auch im IE 8 so? Ich hatte die JScript-Jungs bei der Veröffentlichung des Deviation-Papers so verstanden, dass sie diese bekannten Abweichungen eigentlich reparieren wollten.
Tim
Danke für die ausführliche Erklärung :)
Ich bin übrigens auf das Problem gestoßen, nachdem ich die alert() methode überschrieben habe und anschließend noch der Object-klasse eine Methode verpasst habe:
Object.prototype.alert = function()( return alert( this ); };
Dann wurde mal das eine alert aufgerufen, mal das andere...
Die Methode ist ziemlich cool:
"ein String...".alert().split('').alert().join(',').alert();
aber wohl zu unvorhersagbar..
Ich bin heute durch Rumprobieren auf ein Verhalten von Javascript gestoßen, dass ich nicht ganz verstehe.
Ich bin heute durch Surfen im SELFHTML-Forum auf ein Rumprobieren gestoßen, das ich nicht ganz verstehe.
Object.prototype.foo = function(){
alert("22222222222");
}
this.methodX = function methodX(){
foo();
}
Meine Frage: Wiesu tut sie das? Wiesu denn bluß?
Und weiß sie, was sie sich damit einhandelt?
Worauf willst du mit diesen Tests hinaus?
Mathias