Zugriff aus privater Methode in Prototype-Objekt auf this?
Olaf Schneider
- javascript
Hallo,
• Step 1: Ich habe ein Objekt Foo, dessen Konstruktorfunktion ich eine id übergeben kann. Die Methode getId() gibt mir die id zurück.
var Foo = function(id) {
this.getId = function() {
return id;
}
}
myFoo = new Foo(23);
alert(myFoo.getId()); // -> 23
-> klappt prima.
• Step 2: Foo soll weitere Funktionalität erhalten, die ich über das Prototyp-Objekt von Foo zur Verfügung stelle:
var Foo = function(id) {
this.getId = function() {
return id;
}
}
Foo.prototype = new function() {
this.getSpecialId = function() {
return this.getId() + 1;
}
this.showSpecialId = function() {
alert(this.getSpecialId());
}
}
myFoo = new Foo(23);
myFoo.showSpecialId(); // -> 24
-> klappt auch prima.
• Step 3: Jetzt habe ich folgende Überlegung: Ich möchte gerne, dass nicht alle Methoden öffentlich sind, z.B. getSpecialId() soll privat sein. Mein erster Gedanke war, genau so zu verfahren, als stände sämtlicher Code in der Konstruktor-Funktion und nicht im Prototype-Objekt:
var Foo = function(id) {
this.getId = function() {
return id;
}
}
Foo.prototype = new function() {
var that = this;
var getSpecialId = function() {
return that.getId() + 1;
}
this.showSpecialId = function() {
alert(getSpecialId());
}
}
myFoo = new Foo(23);
myFoo.showSpecialId(); // -> Fehler 'that.getId is not a function'
Allerdings bezieht sich jetzt wohl „that“ in getSpecialId() ausschließlich auf Foo.prototype und Foo ist ihm völlig unbekannt. Daher bricht das Script mit einem Fehler ab, da die Methode getId() unbekannt ist.
Ist es möglich, in privaten Methoden innerhalb von Foo.prototype auf Methoden oder Eigenschaften von Foo zuzugreifen?
Gruß
Olaf
Foo.prototype = new function() {
var that = this;
var getSpecialId = function() {
return that.getId() + 1;
}
this.showSpecialId = function() {
alert(getSpecialId());
}
}
Allerdings bezieht sich jetzt wohl „that“ in getSpecialId() ausschließlich auf Foo.prototype
Ja, das ist ganz logisch.
Du rufst eine Funktion mit new auf. Es wird ein leeres Object erzeugt. In der Funktion legst du lokale Variablen an (that, getSpecialId). Dem Object fügst du nun eine Methode hinzu. Die Rückgabe des Funktionsaufrufs speicherst du in Foo.prototype. Das heißt nun: Alle Instanzen von Foo haben eine Methode showSpecialId. Darin hast du Zugriff auf that (zeigt auf Foo.prototype) und getSpecialId.
Ist es möglich, in privaten Methoden innerhalb von Foo.prototype auf Methoden oder Eigenschaften von Foo zuzugreifen?
Das Prototyp-Objekt hat mit den Instanzen nichts zu tun, eine Verbindung besteht erst einmal nicht. Die einzige Verbindung besteht darin, dass die Methoden aus dem Prototyp-Objekt an die Instanz kopiert werden. Dann sind sie dessen Unterobjekte, können über instanz.methode() aufgerufen werden und this zeigt wie gewünscht auf die Instanz (alleine wegen dieser Unterobjekt-Beziehung und alleine wegen dieses Aufrufs).
Ich wüsste daher nicht, wie du beim Anlegen des Prototyp-Objekts in der mit new aufgerufenen Funktion an die Instanz herankommst. Die einzige Stelle, wo du auf das Instanzobjekt Zugriff hast, ist in der öffentlichen Funktion. Jetzt kannst höchstens folgendes tun:
privateMethode(this) // Instanzobjekt übergeben
privateMethode.call(this) // Im Kontext der Instanz ausführen
Aber ich frage mich, warum du nicht die klassische Schreibweise ohne Prototyp-Objekt wählst, um private Member zu emulieren:
function Konstruktor () {
var instanz = this;
var privateMethode = function () { alert(instanz); };
this.öffentlicheMethode = function () { privateMethode(); };
}
Hier hast du Zugriff auf die Instanz und kannst eine Referenz darauf anlegen, die durch den Closure-Effekt in der privaten Methode verfügbar ist. Das hat natürlich seine Nachteile, u.a. wird die Methode immer neu angelegt, aber es löst zumindest dein Problem.
Mathias
Aber ich frage mich, warum du nicht die klassische Schreibweise ohne Prototyp-Objekt wählst
Gut, weil du die Objekte nachträglich außerhalb des Konstruktors prototypisch um private Methoden erweitern willst. Das geht zwar, wie du demonstriert hast, sie sind dann aber nur für die hinzugefügten öffentlichen Methoden verfügbar und Zugriff auf die Instanz hast du an dieser Stelle nicht.
Der Konstruktor und die öffentlichen Methoden sind wie gesagt die einzigen Stellen, wo du Zugriff auf die Instanz hast. Und die ganze Magie von privaten Membern in JavaScript liegt in Closures:
Diese Voraussetzungen hast du nur im Konstruktor, wenn du dort alle privaten und alle öffentlichen Member notierst.
Möglicherweise gibt es Helferfunktionen zum Erzeugen von Konstruktoren, mit denen das Hinzufügen von privaten Membern möglich ist. Dann sähe der Zugriff auf sie aber auch ganz anders aus.
Mathias
Hallo Mathias,
vielen Dank für Deine hilfreiche, ausführliche Antwort. Ich hatte auch vermutet, dass mein Vorhaben nicht möglich ist, was Du mir bestätigst.
Mir bleiben damit vermutlich zwei Möglichkeiten:
1. auf das Prototypobjekt verzichten und alle Methoden in den Konstruktor schreiben. Damit sind die öffentlichen Methoden priviligiert und haben Zugriff auf die privaten.
Vorteile:
2. alle Eigenschaften und Methoden öffentlich machen, damit Konstruktor und Prototype-Objekt miteinander kommunizieren können.
Vorteile:
Welche Herangehensweise ich wann wähle, ist damit vom Kontext abhängig.
Schöne Grüße,
Olaf
Hi,
- auf das Prototypobjekt verzichten und alle Methoden in den Konstruktor schreiben. Damit sind die öffentlichen Methoden priviligiert und haben Zugriff auf die privaten.
Vorteile:
- Kapselung durch private Methoden ist möglich
- Code ist leichter für Dritte zu verstehen
Nachteile:
- Alle Methoden liegen pro Objekt vor, kosten also mehr Speicherplatz
Da muss ich dir leider unrecht geben. Methoden, die innerhalb des Konstruktors definiert sind, machen für mich den Code nur unleserlicher und schlechter zu verstehen und zu warten. Closures machen die Sache eben nicht einfacher.
- alle Eigenschaften und Methoden öffentlich machen, damit Konstruktor und Prototype-Objekt miteinander kommunizieren können.
Vorteile:
- Sparsamerer Speicherverbrauch
Nachteile:- Kapselung bestimmter Methoden oder Eigenschaften ist nicht mehr möglich.
Das wäre meine favorisierte Vorgehensweise. Zwar sind private Methoden dann nicht wirklich privat, aber so ist es nun mal. Die Wartbarkeit, Lesbarkeit und der geringere Speicherplatzbedarf sind für mich wichtiger als die tatsächliche Kapselung.
Microsoft hat sich in seinem Client Framework übrigens auch für diesen Weg entschieden, und empfiehlt z.B. private Methoden durch einen _ am Anfang zu kennzeichnen.
MyClass.prototype =
{
_private: function()
{
},
publicMethod: function()
{
}
};
ist doch wesentlich einfacher zu überblicken, als alles im Konstruktor zu notieren, mit var oder this. Außerdem ähnelt es dem Sprachbild anderer OO Sprachen viel mehr, was meiner Meinung nach ein großes Plus für die Wartbarkeit durch Dritte darstellt.
Gruß.
Hallo Christian,
- auf das Prototypobjekt verzichten und alle Methoden in den Konstruktor schreiben. Damit sind die öffentlichen Methoden priviligiert und haben Zugriff auf die privaten.
Vorteile: [im Zitat gekürzt]
- Code ist leichter für Dritte zu verstehen
Da muss ich dir leider unrecht geben. Methoden, die innerhalb des Konstruktors definiert sind, machen für mich den Code nur unleserlicher und schlechter zu verstehen und zu warten. Closures machen die Sache eben nicht einfacher.
Wenn ich Eigenschaften privat halten möchte und Accessormethoden definiere, dann kann ich das ja nur im Konstruktor tun. Das würde dazu führen, dass sich ein Teil der Methoden in diesem und ein anderer im Prototype-Objekt befindet. Das fände _ich_ schwerer zu lesen als alle Methoden an einer Stelle.
Wann man aber ohnehin sagt, dass man private nur durch Konvention (Unterstrichpräfix) auszeichnet, kann man natürlich alle dem Konstruktor übergebenden Parameter dort mit ins Objekt übernehmen (this.id = id; …) und kann so sämtliche Methoden ins Prototype-Objekt verlagern. In diesem Fall würde ich Dir mit der Lesbarkeit Recht geben.
Gruß
Olaf
[latex]Mae govannen![/latex]
Microsoft hat sich in seinem Client Framework übrigens auch für diesen Weg entschieden, und empfiehlt z.B. private Methoden durch einen _ am Anfang zu kennzeichnen.
Und (unter Anderem) Douglas Crockford empfiehlt, genau das _nicht_ zu tun [1] ;) Ist ohnehin eine Glaubensfrage [2].
[1] http://javascript.crockford.com/code.html Sub-Überschrift „Names“
[2] Usenet-Thread {Anzeige über die Krücke Google-Groups}
Cü,
Kai
Da muss ich dir leider unrecht geben. Methoden, die innerhalb des Konstruktors definiert sind, machen für mich den Code nur unleserlicher und schlechter zu verstehen und zu warten. Closures machen die Sache eben nicht einfacher.
Du musst die Methoden nicht im Konstruktor definieren, es reicht, wenn du ihnen die privaten Daten verfügbar machst.
Das wäre meine favorisierte Vorgehensweise. Zwar sind private Methoden dann nicht wirklich privat, aber so ist es nun mal. Die Wartbarkeit, Lesbarkeit und der geringere Speicherplatzbedarf sind für mich wichtiger als die tatsächliche Kapselung.
Es gibt dann schlicht keine privaten Methoden. "Ein bisschen privat" geht nicht.
Microsoft hat sich in seinem Client Framework übrigens auch für diesen Weg entschieden, und empfiehlt z.B. private Methoden durch einen _ am Anfang zu kennzeichnen.
Der Unterstrich am Anfang bedeutet aber nicht "private Methode", denn man kann sie von außen ja erreichen. Es bedeutet eher "diese Methode bitte nicht benutzen", wobei es wohl offensichtlicher Blödsinn ist, eine Methode öffentlich verfügbar zu machen, aber zu sagen, sie solle nicht benutzt werden.
(...)
ist doch wesentlich einfacher zu überblicken, als alles im Konstruktor zu notieren, mit var oder this. Außerdem ähnelt es dem Sprachbild anderer OO Sprachen viel mehr, was meiner Meinung nach ein großes Plus für die Wartbarkeit durch Dritte darstellt.
Die meisten anderen, vor allem die bekannteren, OO-Sprachen funktionieren aber anders als JavaScript.
Hallo Timo,
Du musst die Methoden nicht im Konstruktor definieren, es reicht, wenn du ihnen die privaten Daten verfügbar machst.
wie kann ich denn private Daten, die im Konstruktor gesetzt wurden den Methoden im Prototype-Objekt bekannt machen. Das scheint ja imho nicht möglich zu sein.
Gruß
Olaf
Du musst die Methoden nicht im Konstruktor definieren, es reicht, wenn du ihnen die privaten Daten verfügbar machst.
wie kann ich denn private Daten, die im Konstruktor gesetzt wurden den Methoden im Prototype-Objekt bekannt machen. Das scheint ja imho nicht möglich zu sein.
Ist es auch nicht. Ich weiß auch nicht warum dir suggeriert wäre du hättest eine andere Möklichkeit, wenn du prviligierte Funktionen brauchst, müssen diese im Kontruktor definiert werden.
Struppi.
Ist es auch nicht. Ich weiß auch nicht warum dir suggeriert wäre du hättest eine andere Möklichkeit, wenn du prviligierte Funktionen brauchst, müssen diese im Kontruktor definiert werden.
Nochmal:
Ich weiß auch nicht warum dir suggeriert wird, du hättest eine andere Möglichkeit. wenn du prviligierte Funktionen brauchst, müssen diese im Kontruktor definiert werden.
Struppi.
wie kann ich denn private Daten, die im Konstruktor gesetzt wurden den Methoden im Prototype-Objekt bekannt machen.
JavaScript hat nicht die OOP-Fähigkeiten anderer bekannter Sprachen und es ist problematisch, das Konzept der Kapselung auf JavaScript zu übertragen. Privatheit gibt es in JavaScript nur durch Closures. Das ist erstmal was ganz anderes, ein funktionales statt ein objektorientiertes Feature, und ist auch nicht an die Verwendung im Zusammenhang mit Konstruktoren und Instanzen gebunden.
Aus funktionaler Sicht ist dein Ausgangsbeispiel mit K.prototype = new function () {} eigentlich sehr passend und es wäre auch sinnig, wenn du die durch Closure verfügbaren Methoden mit call bzw. apply im Kontext der Instanz ausführst. Das wäre m.M.n. JavaScript-typisch, alles andere wären Versuche, gänzlich anders organisiertes OOP aus anderen Sprachen in JavaScript umzusetzen. Das geht nicht, das ist auch nicht unbedingt sinnvoll und wird den Features von JavaScript nicht gerecht.
Wie gesagt könnte man Privatheit simulieren, indem man die Methoden über eine Funktion hinzufügt, die sie alle kapselt:
var K = Class.create({
private : {
pr : function () {}
},
public : {
pu : function () {
this.private.pr();
}
}
});
var i = new K;
i.pu();
Class.extend(K, {
private : {
pr2 : function () {}
},
public : {
pu2 : function () {
this.private.pr();
this.private.pr2();
}
}
});
i = new K;
i.pu();
i.pu2();
Class.create und extend müssten natürlich geschrieben werden. Die öffentlichen Methoden würden so gekapselt, dass in ihnen this.private verfügbar ist, aber die Instanz hätte diesen Member nur kurzzeitig.
Ist zwar auch nicht schön, dieses ständige Anlegen und Löschen von Referenzen, aber die Aufgabenstellung verhindert wie gesagt JavaScript-typische Lösungen. Ich würde das nicht so umsetzen, sondern die funktionalen Fähigkeiten von JavaScript ernst nehmen.
Mathias
wie kann ich denn private Daten, die im Konstruktor gesetzt wurden den Methoden im Prototype-Objekt bekannt machen.
JavaScript hat nicht die OOP-Fähigkeiten anderer bekannter Sprachen und es ist problematisch, das Konzept der Kapselung auf JavaScript zu übertragen. Privatheit gibt es in JavaScript nur durch Closures.
Man kann Prima in JS private Eigenschaften und Funktionen in der Konstruktorfunktion kapseln, nur eben nicht im Prototype. Wo siehst du da ein Problem?
Struppi.
Man kann Prima in JS private Eigenschaften und Funktionen in der Konstruktorfunktion kapseln
Das habe ich nicht in Frage gestellt.
nur eben nicht im Prototype.
Kann man doch, und zwar mit demselben Mechanismus, mit dem man es in der Konstruktorfunktion kann. Nur muss man sich dann eine andere Methode ausdenken, um in ihnen Zugriff auf die Instanz zu haben.
Wo siehst du da ein Problem?
Ein Problem damit habe ich nicht, jedoch sehe ich ein Problem darin, die Denkweise von JavaScript-fremden OOP auf JavaScript-OOP zu übertragen, anstatt über Lösungen in der JavaScript-Logik nachzudenken.
Mathias
Hallo,
Ehrlich gesagt verstehe ich nicht, was dein Posting sagen soll. Könntest du das nochmal anders formulieren?
Mathias
Ehrlich gesagt verstehe ich nicht, was dein Posting sagen soll. Könntest du das nochmal anders formulieren?
Es ging mir darum, dass Olaf meinte, dass es unübersichtlich sei, Methoden im Konstruktor zu definieren. Zusätzlich greift ja auch das Speicher-Argument, sehr lange Methoden würden pro Objekt erzeugt und würden viel Speicher verbrauchen.
Man kann aber die Methoden auch außerhalb des Konstruktors definieren, man muss dann nur dafür sorgen, dass sie Zugriff auf die "versteckten" Daten haben bzw. Methoden erzeugen, die dann die Funktionen von außerhalb nutzen.
Wie in meinem anderen Beitrag beispielsweise.
Hallo,
Es ging mir darum, dass Olaf meinte, dass es unübersichtlich sei, Methoden im Konstruktor zu definieren.
nein, das meinte ich nicht,
Zusätzlich greift ja auch das Speicher-Argument, sehr lange Methoden würden pro Objekt erzeugt und würden viel Speicher verbrauchen.
aber das ;-)
Gruß
Olaf
Es ging mir darum, dass Olaf meinte, dass es unübersichtlich sei, Methoden im Konstruktor zu definieren.
nein, das meinte ich nicht,
Ups, stimmt. Das war Christian.
Da muss ich dir leider unrecht geben. Methoden, die innerhalb des Konstruktors definiert sind, machen für mich den Code nur unleserlicher und schlechter zu verstehen und zu warten. Closures machen die Sache eben nicht einfacher.
Nachtrag: Wenn es dir nur darum geht, die Methoden irgendwo einheitlich zu sammeln, ist ja vielleicht das hier was für dich:
var MyClass=(function(){
// Helper function, should be outsourced.
function bind(obj, priv, func){
return function(){
var args=[priv];
for (var i=0,l=arguments.length;i<l;i++){
args.push(arguments[i]);}
return func.apply(obj, args);}}
// Constructor
function C(){
var priv={x:Math.random()};
this.toString=function(){return '[C x='+priv.x+']';};
for (var method in privateMethods){
priv[method]=bind(this, priv, privateMethods[method]);}
for (var method in privilegedMethods){
this[method]=bind(this, priv, privilegedMethods[method]);}}
// Private methods
var privateMethods={
setX: function(priv, x){
priv.x=x;}};
// Privileged methods
var privilegedMethods={
getX:function(priv){
return priv.x;},
mulX:function mulX(priv, factor){
priv.x*=factor;
return this;}};
// Unprivileged methods
C.prototype={
getDoubleX:function getDoubleX(){
return this.getX()*2;}};
return C;
})();
gruss Olaf,
...
var Foo = function(id) {
this.getId = function() {
return id;
}
}Foo.prototype = new function() {
var that = this;
var getSpecialId = function() {
return that.getId() + 1;
}
this.showSpecialId = function() {
alert(getSpecialId());
}
}myFoo = new Foo(23);
myFoo.showSpecialId(); // -> Fehler 'that.getId is not a function'
> ...
> Ist es möglich, in privaten Methoden innerhalb von Foo.prototype
> auf Methoden oder Eigenschaften von Foo zuzugreifen?
nein, so wie Du es gerne betreiben moechtest, ueberhaupt nicht. die argumente,
die dagegen sprechen, werden Dir ja schon von Mathias in den zwei folgenden,
aus gruenden der linearitaet hier noch mal velinkten und zitierten posts
genannt ...
... aus <https://forum.selfhtml.org/?t=180370&m=1191665> ...
> > wie kann ich denn private Daten, die im Konstruktor gesetzt wurden
> > den Methoden im Prototype-Objekt bekannt machen.
>
... zitat molily:
> JavaScript hat nicht die OOP-Fähigkeiten anderer bekannter Sprachen
> und es ist problematisch, das Konzept der Kapselung auf JavaScript
> zu übertragen. Privatheit gibt es in JavaScript nur durch Closures.
> Das ist erstmal was ganz anderes, ein funktionales statt ein
> objektorientiertes Feature, und ist auch nicht an die Verwendung
> im Zusammenhang mit Konstruktoren und Instanzen gebunden.
>
> Aus funktionaler Sicht ist dein Ausgangsbeispiel mit K.prototype = new function () {}
> eigentlich sehr passend und es wäre auch sinnig, wenn du die durch
> Closure verfügbaren Methoden mit call bzw. apply im Kontext der
> Instanz ausführst. Das wäre m.M.n. JavaScript-typisch, alles andere
> wären Versuche, gänzlich anders organisiertes OOP aus anderen Sprachen
> in JavaScript umzusetzen. Das geht nicht, das ist auch nicht unbedingt
> sinnvoll und wird den Features von JavaScript nicht gerecht.
> ...
... und aus <https://forum.selfhtml.org/?t=180370&m=1191683> ...
> > Man kann Prima in JS private Eigenschaften und Funktionen in
> > der Konstruktorfunktion kapseln
> ...
> > nur eben nicht im Prototype.
> ...
> > Wo siehst du da ein Problem?
>
... zitat molily:
> Ein Problem damit habe ich nicht, jedoch sehe ich ein Problem
> darin, die Denkweise von JavaScript-fremden OOP auf JavaScript-
> OOP zu übertragen, anstatt über Lösungen in der JavaScript-Logik
> nachzudenken.
@Olaf
es stellen sich zwei fragen; warum willst Du an so einer delikaten stelle
modularisierten code schreiben; warum versuchst Du genau dies ueber den
[protoype] des [[Foo]]-constructors zu implementieren.
zuerst fasse ich die ideen des Sprachkonzepts nochmal kurz in eigene worte,
dannach folgen beispielhaft loesungen, die sich schulmaessig an Deinem code
orientieren muessen und damit die frage nach ihrer bedeutung fuer die praxis
nicht beantworten koennen.
- Der als ECMAScript (ECMA 262) standardisierte Sprachkern von JavaScript beschreibt eine moderne, schlanke, objektorientierte aber klassenlose Skriptsprache, die dennoch allen objektorientierten Programmierparadigmen unter anderem auch - aber eben nicht ausschließlich - auf der Basis von Prototypen gerecht wird.
Obwohl im Grunde eine funktionale Skriptsprache, läßt sich in JavaScript sowohl prozedural als auch rein funktional bzw. objektorientiert programmieren.
ECMA 262 kann damit ohne weiteres auch als Multiparadigmensprache bezeichnet werden.
- In JavaScript representieren sich alle Daten bis auf die Typen [Undefined] und [Null] bzw. bis auf die primitiven Werte [boolean], [number] und [string] als Objekte. Funktionen sind ebenfalls Objekte, deren im Funktionsrumpf gebundenen Anweisungen über den call-Operator bzw. über call-Methoden ausgeführt werden.
- Gekapselte Daten sind lokale Werte bzw. Objekte einer Funktion. Diese begrenzte \*Sichtbarkeit\* von Daten kann bei Datenstrukturen durch ineinander verschachtelte Funktionen (Funktion in Funktion) gezielt ausgenutzt und über Referenzierungskonzepte ebenso gezielt getunnelt werden.
- Schon auf dieser Grundlage läßt sich fuer alle nativen JavaScript-Objekte das Signal-Slot-Konzept implementieren, sodaß ereignisorientiertes Programmieren, auch losgelöst von DOM-Events, allein mit den Mitteln des Sprachkerns möglich ist.
- Vererbung wiederum erfolgt in JavaScript ausschließlich über Delegation; entweder direkt über eine der call-Methoden oder implizit über den Objekt- Prototypen eines jeden Objekt-Konstruktors. Letztgenannter leistet dabei die Abstraktion zur Vererbung (im Sinne von \*ist ein\*), während die zuerst angesprochenen Methoden der Umsetzung des Aggregationskonzepts (\*hat ein\*) dienlich sind.
siehe dazu auch den diskussions- bzw. den benutzer-link auf der deutschen wikipedia zu JavaScript:
- [Neues Fass aufmachen: Sprachklassifizierung allgemein und Diskussion der aktuellen Einleitung speziell](http://de.wikipedia.org/wiki/Diskussion:JavaScript#Neues_Fass_aufmachen:_Sprachklassifizierung_allgemein_und_Diskussion_der_aktuellen_Einleitung_speziell)
- [Intro-Sprachkonzept - Sprachklassifizierung - Werte, Objekte des Sprachkerns, Objektstrukturen - Funktionen als Datentypen - Funktionen als Konstruktoren von Objekten eines bestimmten Typs - prototypenbasierte Vererbung](http://de.wikipedia.org/wiki/Benutzer_Diskussion:Pseliger)
eine loesung Deines problems muss besonders absatz 3 beruecksichtigen.
wir suchen ein geeignetes referenzierungskonzept, welches den beiden
fuereinander \*kontextblinden\* modulen genau einen gueltigen kontext
zur verfuegung stellt.
in Deinem beispiel moechtest Du gerne das zusaetzliche verhalten je einer
privaten bzw. oeffentlichen methode [getSpecialId] bzw. [showSpecialId]
zusaetzlich zum verhalten einer jeden [[Foo]]-instanz implementieren.
schon die wortwahl klingt eher nach interface als nach prototypischer
erweiterung - in einem ersten wurf kann das z.b. so aussehen:
~~~javascript
/*
wie immer code bitte mit *copy und paste*
in der [link:http://jconsole.com/@title=jconsole] zum laufen bringen:
*/
var SpecialIdFunctor = (function (that) { // [[SpecialId]] -Functor(, -Interface, -Behavior, ... -Monad?)
var getSpecialId = (function () {
return (that.getId() + 1);
});
this.showSpecialId = (function() {
//alert(getSpecialId());
return getSpecialId();
});
});
var Foo = (function (id) { // [[Foo]] constructor
this.getId = (function () {
return id;
});
});
var myFoo = new Foo(23);
print(myFoo.id); // undefined
print(myFoo.getId()); // 23
print(myFoo.getSpecialId); // undefined
print(myFoo.showSpecialId); // undefined
SpecialIdFunctor.call(myFoo, myFoo); // applying the [Functor](, [Interface], [Behavior], ... [Monad]?) onto [myFoo]
print(myFoo.id); // undefined
print(myFoo.getId()); // 23
print(myFoo.getSpecialId); // undefined
print(myFoo.showSpecialId()); // 24
diese loesung laesst es zu, dass jede einzelne [[Foo]]-instanz um
eigenschaften des hier [[SpecialIdFunctor]] genannten interfaces
erweitert werden kann und eben nicht zwangslaeufig um diese
erweitert wird.
die naechste loesung leitet sich aus dem obigen beispiel ab, setzt
einen dem interface verpflichteten automatismus um, und kapselt dabei
die gesamte funktionalitaet unter dem constructor [[Foo]]:
/*
[link:http://jconsole.com/]
*/
var Foo = (function (/*id*/) { // [[Foo]] constructor wrapper
var SpecialIdFunctor = (function (that) { // [[SpecialId]] -Functor(, -Interface, -Behavior, ... -Monad?)
var getSpecialId = (function () {
return (that.getId() + 1);
});
this.showSpecialId = (function() {
//alert(getSpecialId());
return getSpecialId();
});
});
var FooConstructor = (function (id) { // [[Foo]] constructor
SpecialIdFunctor.call(this, this); // applying the [Functor](, [Interface], [Behavior], ... [Monad]?)
this.getId = (function () {
return id;
});
});
return FooConstructor;
})();
var myFoo = new Foo(23);
print(myFoo.id); // undefined
print(myFoo.getId()); // 23
print(myFoo.getSpecialId); // undefined
print(myFoo.showSpecialId()); // 24
auch im letzten entwurfsmuster gibt es noch genug gruende modularisiert
zu schreiben, auch wenn es im moment auf den ersten blick erstmal nicht
viel mehr als das argument der codepflege hergibt - komplexere anwendungen
koennen hier aber sowohl von kapselung als auch von schnittstellenvererbung
profitieren.
noch mehr spielkram in diese richtung findet sich unter:
so long - peterS. - pseliger@gmx.net
Hallo Peter,
vielen Dank erst einmal für Deine ausführliche und interessante Antwort.
Ich komme mal direkt auf Dein zweites Beispiel zu sprechen (meine Kommentare stehen hinter der entsprechenden Codezeile):
/*
[link:http://jconsole.com/]
*/var Foo = (function (/id/) { // [[Foo]] constructor wrapper
Dieser Code wird jedes Mal ausgeführt, wenn ich new Foo() aufrufe. Richtig?
var SpecialIdFunctor = (function (that) { // [[SpecialId]] -Functor(, -Interface, -Behavior, ... -Monad?)
Die in Foo lokale Variable SpecialIdFunctor wird also auch jedes Mal neu aufgebaut.
var getSpecialId = (function () {
return (that.getId() + 1);
});
that ist ein Verweis auf FooConstructor, also ist die Methode getId() bekannt. Das ist mir klar.
this.showSpecialId = (function() {
return getSpecialId();
});
getSpecialId ist vorher lokal definiert, auch klar.
});
warum umschließt Du die function mit Klammern, wenn Du sie nicht ausführst. Deine Coding Guidelines?
var FooConstructor = (function (id) { // [[Foo]] constructor
SpecialIdFunctor.call(this, this); // applying the [Functor](, [Interface], [Behavior], ... [Monad]?)
this.getId = (function () {
return id;
});
});return FooConstructor;
})();
auch der Rest des Codes ist mir nach und nach klar geworden. Was ich allerdings nicht verstehe, wo jetzt die Vorteil gegenüber dem intuitiverem folgenden Code liegen:
[code lang=javascript]Foo = function(id) {
var that = this;
var getSpecialId = function() {
return that.getId() + 1;
}
this.getId = function() {
return id;
}
this.showSpecialId = function() {
return getSpecialId();
}
}
Sie scheint mir auf den ersten Blick innen ähnlich und nach außen identisch aufgebaut, ohne Nachteile zu haben.
Meine ursprüngliche Frage zielte darauf ab, bestimmte Teile wirklich nur einmal im Prototype zu haben, damit bestimmte Funktionen (aber eben nicht alle) nur einmal vorhanden sind und nicht in jeder Objektinstanz.
Ich habe allerdings noch nicht überprüft, in wieweit die von Dir, Peter
neu vorgestellte Technologie auch auf mein Problem anwenden lässt.
Tipps …?
Gruß
Olaf
hallo again Olaf,
...
Ich komme mal direkt auf Dein zweites Beispiel zu sprechen
(meine Kommentare stehen hinter der entsprechenden Codezeile):
...
var Foo = (function (/id/) { // [[Foo]] constructor wrapper
...
> Dieser Code wird jedes Mal ausgeführt, wenn ich new Foo() aufrufe.
> Richtig?
nein, die von mir holprig als \*constructor wrapper\* bezeichnete
»function expression« wird in einem rutsch initialisiert und eben
genau einmal ausgefuehrt.
ihr rueckgabewert auf [[Foo]] ist dann der konstruktor aller
zukuenftigen [Foo]-objekte. im \*wrapper\* firmiert diese funktion
noch unter [[FooConstructor]]. da nun ebendieser konstruktor im
sichtbarkeitsbereich des wrappers angelegt wurde, kann er immer
den im selben funktions-stack angelegten [[SpecialIdFunctor]]
referenzieren, auch wenn letzgenannter von \*aussen\* nicht mehr
sichtbar ist - ein klassischer »closure« - und in kurzen worten
nochmal am pseudocode:
~~~javascript
var Foo = (function (/*id*/) { // [[Foo]] constructor wrapper
/*
[[Foo]] constructor wrapper ...
*/
var SpecialIdFunctor = (function (that) { // [[SpecialId]] -Functor(, -Interface, -Behavior, ... -Monad?)
/*
... kapselt sowohl diesen funktor ...
*/
});
var FooConstructor = (function (id) { // [[Foo]] constructor
SpecialIdFunctor.call(this, this); // applying the [Functor](, [Interface], [Behavior], ... [Monad]?)
/*
... als auch diesen konstruktor, ...
*/
});
return FooConstructor;/*
... welcher unmittelbar dannach auf [[Foo]] referenziert wird.
*/
})();
[[SpecialIdFunctor]] und [[FooConstructor]] *leben* in einer art
anonymen / unzugaenglichem / nicht adressierbaren universum.
[[FooConstructor]] wiederum ist nur ueber [[Foo]] zu erreichen.
bis hierhin ist also noch keinerlei instanziierung erfolgt.
das passiert erst beim anlegen von [Foo]-objekten ueber den
[new]-operator in verbindung mit dem durch [[Foo]] referenzierten
konstruktor.
dabei macht sich dann auch der [[SpecialIdFunctor]] mit jeder
dieser instanzen bekannt und hinterlaesst seine signatur.
was ich vermute, aber weder recherchieren noch beweisen kann, ist,
dass hier, analog zu prototypischen erweiterungen, der kontext der
in diesem bsp. bisher einzigen oeffentliche funktoren-methode
[showSpecialId] erst zur laufzeit beim zugriff auf ebenjene methode
durch ein [Foo]-objekt aufgeloest wird.
ich werf' da einfach mal ein paar begriffe in den raum:
... spaete bindung? *superspaete bindung*? ...
... ich stochere da im nebel und bedarf selber der dringenden
aufklaerung ueber das echte laufzeitverhalten dieser speziellen
konstellation.
... der Rest des Codes ist mir nach und nach klar geworden.
Was ich allerdings nicht verstehe, wo jetzt die Vorteil gegenüber
dem intuitiverem folgenden Code liegen:
Foo = function(id) {
var that = this;
var getSpecialId = function() {
return that.getId() + 1;
}this.getId = function() {
return id;
}this.showSpecialId = function() {
return getSpecialId();
}}
>
> Sie scheint mir auf den ersten Blick innen ähnlich und nach
> außen identisch aufgebaut, ohne Nachteile zu haben.
falls ich mit meinen vermutungen richtig liege, geht die von mir
vorgestellte variante schonender mit arbeitsspeicher um, da jede
einzelne [[Foo]]-instanziierung weniger overhaed erzeugt, als es
der code Deines beispiels tut. vielleicht ist mein konstrukt sogar
schneller. klarheit schafft hier nur ein performance-test.
ich beuge mich auch jedem urteil; wobei noch viel interessanter
waere, wie die unterschiedlichen scripting hosts dabei abschneiden.
> Meine ursprüngliche Frage zielte darauf ab, bestimmte Teile
> wirklich nur einmal im Prototype zu haben, damit bestimmte
> Funktionen (aber eben nicht alle) nur einmal vorhanden sind
> und nicht in jeder Objektinstanz.
wenn alles richtig gedacht war, leistet mein muster genau das.
so long - peterS. - pseliger@gmx.net
--
»Because objects in JavaScript are so flexible, you will want to think differently about class hierarchies.
Deep hierarchies are inappropriate. Shallow hierarchies are efficient and expressive.« - [Douglas Crockford](http://javascript.crockford.com/)
[ie:( fl:) br:> va:( ls:& fo:) rl:) n3;} n4:} ss:} de:µ js:} mo:? zu:\]](http://www.peter.in-berlin.de/projekte/selfcode/?code=ie%3A%28+fl%3A%29+br%3A%3E+va%3A%28+ls%3A%26+fo%3A%29+rl%3A%29+n3%3B%7D+n4%3A%7D+ss%3A%7D+de%3A%B5+js%3A%7D+mo%3A%3F+zu%3A%5D)
Hallo Peter,
var Foo = (function (/id/) { // [[Foo]] constructor wrapper
...
> > Dieser Code wird jedes Mal ausgeführt, wenn ich new Foo() aufrufe.
> > Richtig?
> nein, die von mir holprig als \*constructor wrapper\* bezeichnete
> »function expression« wird in einem rutsch initialisiert und eben
> genau einmal ausgefuehrt.
> […]
Ich glaube, erst jetzt habe ich Dein Konstukt wirklich verstanden. Klar – Foo wird ja nur einmal zur Laufzeit ausgeführt und mit dem Rückgabewert der anonymen Funktion besetzt.
> was ich vermute, aber weder recherchieren noch beweisen kann, ist,
> dass hier, analog zu prototypischen erweiterungen, der kontext der
> in diesem bsp. bisher einzigen oeffentliche funktoren-methode
> [showSpecialId] erst zur laufzeit beim zugriff auf ebenjene methode
> durch ein [Foo]-objekt aufgeloest wird.
> […]
> ... ich stochere da im nebel und bedarf selber der dringenden
> aufklaerung ueber das echte laufzeitverhalten dieser speziellen
> konstellation.
Hm, das interessiert mich auch. Javascript ist ganz schön mächtig, meiner Meinung nach aber nicht immer auf den ersten Blick intuitiv.
[…]
> falls ich mit meinen vermutungen richtig liege, geht die von mir
> vorgestellte variante schonender mit arbeitsspeicher um, da jede
> einzelne [[Foo]]-instanziierung weniger overhaed erzeugt, als es
> der code Deines beispiels tut. vielleicht ist mein konstrukt sogar
> schneller. klarheit schafft hier nur ein performance-test.
Volle Zustimmung.
> > Meine ursprüngliche Frage zielte darauf ab, bestimmte Teile
> > wirklich nur einmal im Prototype zu haben, damit bestimmte
> > Funktionen (aber eben nicht alle) nur einmal vorhanden sind
> > und nicht in jeder Objektinstanz.
>
> wenn alles richtig gedacht war, leistet mein muster genau das.
Ja, jetzt auf meinen zweiten (äh dritten, äh vierten) Blick ja.
Ich werde an diesem Thema mal dranbleiben und wenn ich interessante Neuigkeiten habe, mich wieder in der einen oder anderen Form melden.
Gruß
Olaf
Hallo,
Anmerkung:
Der Block var SpecialIdFunctor […] kann auch aus dem Block var Foo […] ausgelagert und diesem vorangestellt werden. Die Funktionalität von Foo scheint unverändert, SpecialIdFunctor ist dann jedoch global. Ein Beispiel:
var ExternalIdMixIn = function (that) {
var getExternalId = function () {
return that.getId() + ' external';
};
this.showExternalId = function() {
return getExternalId();
};
};
var Foo = (function () {
var InternalIdMixIn = function (that) {
var getInternalId = function () {
return that.getId() + ' internal';
};
this.showInternalId = function() {
return getInternalId();
};
};
return function (id) {
InternalIdMixIn.call(this, this);
ExternalIdMixIn.call(this, this);
this.getId = function () {
return id;
};
};
return FooConstructor;
})();
var myFoo = new Foo(23);
print(myFoo.getId()); // 23
print(myFoo.showExternalId()); // 23 internal
print(myFoo.showInternalId()); // 23 external
In diesem Falle hätte SpecialIdFunctor eher die Funktion eines MixIns. Lustigerweise hatte ich mit dieser Art Mixin per Function.call() vor zwei, drei Monaten schon experimentiert, aber die Zusammenhänge zu Deinem Beitrag erst gerade eben gesehen.
Gruß
Olaf
Ich beziehe mich auf das zweite Beispiel, wo
das passiert erst beim anlegen von [Foo]-objekten ueber den
[new]-operator in verbindung mit dem durch [[Foo]] referenzierten
konstruktor.
dabei macht sich dann auch der [[SpecialIdFunctor]] mit jeder
dieser instanzen bekannt und hinterlaesst seine signatur.
gilt.
was ich vermute, aber weder recherchieren noch beweisen kann, ist,
dass hier, analog zu prototypischen erweiterungen, der kontext der
in diesem bsp. bisher einzigen oeffentliche funktoren-methode
[showSpecialId] erst zur laufzeit beim zugriff auf ebenjene methode
durch ein [Foo]-objekt aufgeloest wird.
Warum sollte das passieren (siehe unten)?
ich werf' da einfach mal ein paar begriffe in den raum:
... spaete bindung? *superspaete bindung*? ...... ich stochere da im nebel und bedarf selber der dringenden
aufklaerung ueber das echte laufzeitverhalten dieser speziellen
konstellation.
Gehen wir doch einfach mal durch, was bei dem Aufruf von new Foo(id);
(id
irgendein Wert) passiert:
new Foo(id);
Es wird ein neues Objekt angelegt (beachte FooConstructor
).
function (id) {
Diese Funktion wird aufgerufen. id hat dabei den übergebenen Wert.
SpecialIdFunctor.call(this, this);
SpecialIdFunctor
wird im Kontext von this
aufgerufen, mit dem einzigen Argument this
. Dabei passiert Folgendes:
function (that) {
that
ist nun this
aus dem Konstruktor von Foo
.
var getSpecialId=(function(){return(that.getId()+1);});
Es wird eine Funktion erzeugt und in getSpecialId gespeichert.
this.showSpecialId=(function(){return getSpecialId();});
Es wird eine Funktion erzeugt, die die zuvor erzeugte Funktion aufruft (und deren Rückgabewert zurückgibt) und als Eigenschaft von this
gespeichert.
Damit endet der Aufruf von SpecialIdFunctor
, that
und getSpecialId
bleiben erhalten (Closure) und wir kehren zum Konstruktor Foo
zurück.
this.getId=(function(){return id;});
Es wird eine Funktion erzeugt, die id
zurückgibt und als Eigenschaft von this
gespeichert.
Der Konstruktor-Aufruf endet, das erzeugte Objekt wird zurückgegeben.
Ergebnis: Drei Variablen (id
, that
, getSpecialId
), die via Closure erhalten werden, zwei Methoden in this
.
Demgegenüber:
Foo = function(id) {
var that = this;
var getSpecialId = function() {
return that.getId() + 1;
}this.getId = function() {
return id;
}this.showSpecialId = function() {
return getSpecialId();
}}
Ich mache es kurz: Drei Variablen (`id`{:.language-javascript}, `that`{:.language-javascript}, `getSpecialId`{:.language-javascript}), die via Closure erhalten werden, zwei Methoden in `this`{:.language-javascript}.
> falls ich mit meinen vermutungen richtig liege, geht die von mir
> vorgestellte variante schonender mit arbeitsspeicher um, da jede
> einzelne [[Foo]]-instanziierung weniger overhaed erzeugt, als es
> der code Deines beispiels tut. vielleicht ist mein konstrukt sogar
> schneller. klarheit schafft hier nur ein performance-test.
Dein Beispiel macht genau das Gleiche, nur komplizierter. Es müsste sogar langsamer sein, da ein zusätzlicher Funktionsaufruf nötig ist.
> > Meine ursprüngliche Frage zielte darauf ab, bestimmte Teile
> > wirklich nur einmal im Prototype zu haben, damit bestimmte
> > Funktionen (aber eben nicht alle) nur einmal vorhanden sind
> > und nicht in jeder Objektinstanz.
> wenn alles richtig gedacht war, leistet mein muster genau das.
Leider nicht.
--
Reden ist Silber, Schweigen ist Gold, meine Ausführungen sind Platin.
Self-Code: sh:( ch:? rl:( br:> n4:( ie:{ mo:) va:) de:> zu:} fl:| ss:| ls:~ js:|
gruss "God's Boss"
Gehen wir doch einfach mal durch, was bei dem Aufruf von
new Foo(id);
(id
irgendein Wert) passiert:
...
Der Konstruktor-Aufruf endet, das erzeugte Objekt wird zurückgegeben.
Ergebnis: Drei Variablen (id
,that
,getSpecialId
), die via Closure erhalten werden, zwei Methoden inthis
.Demgegenüber:
...
Ich mache es kurz: Drei Variablen (id
,that
,getSpecialId
), die via Closure erhalten werden, zwei Methoden inthis
.falls ich mit meinen vermutungen richtig liege, geht die von mir
vorgestellte variante schonender mit arbeitsspeicher um, da jede
einzelne [[Foo]]-instanziierung weniger overhaed erzeugt, als es
der code Deines beispiels tut. vielleicht ist mein konstrukt sogar
schneller. klarheit schafft hier nur ein performance - test.
Dein Beispiel macht genau das Gleiche, nur komplizierter. Es müsste sogar langsamer sein, da ein zusätzlicher Funktionsaufruf nötig ist.
korrekt - ich hab' mir auch gerade eben den luxus gegoennt, dass ganze
ueberhaupt ersteinmal so schoen kausal zuendezudenken, wie von Dir just
vorexerziert.
Meine ursprüngliche Frage zielte darauf ab, bestimmte Teile
wirklich nur einmal im Prototype zu haben, damit bestimmte
Funktionen (aber eben nicht alle) nur einmal vorhanden sind
und nicht in jeder Objektinstanz.
wenn alles richtig gedacht war, leistet mein muster genau das.
Leider nicht.
jo, das draengte sich mir beim nachdenken ebenfalls mit erschrecken auf.
deswegen hackte ich auch gleich noch einen kurzen dreckigen test in die
tasten, um dann gespannt auf laufzeiten, cpu - und speicher-auslastung
unter den verschiedensten browsern zu achten.
das ergebnis - niederschmetternd! - ich fange mir, wie boese erahnt,
wirklich die volle breitseite:
die anwendung eines solchen interface/funktor/behavior-musters ist
demzufolge nur in genau den faellen sinnvoll, wo einheitliches verhalten
nachtraeglich auf voellig voneinander verschiedene objekte genagelt
werden muss.
den folgenden test bei interesse mal in die jconsole schmeissen:
Number.prototype.times=(function(fct,target){var i=-1,num=Number(this);num=((isFinite(num)&&(typeof fct=="function"))?(parseInt(num)):(i));target=(((typeof target=="undefined")||((typeof target=="obj")&&!target))?(null):(target));while(++i<num){fct.call(target,i,num,fct)}});
var FunkedFoo=(function(){var SpecialIdFunctor=(function(that){var getSpecialId=(function(){return(that.getId()+1)});this.showSpecialId=(function(){return getSpecialId()})});var FooConstructor=(function(id){SpecialIdFunctor.call(this,this);this.getId=(function(){return id})});return FooConstructor})();
var ClassyFoo=(function(id){var that=this;var getSpecialId=(function(){return that.getId()+1});this.showSpecialId=(function(){return getSpecialId()});this.getId=(function(){return id})});
alert("... pause ... proceed ... ");
(function (amountOfTestCycles, isCaching) {
var testCase, time, myId, myFoo, myFooList = [];
if (isCaching) {
testCase = (function () {
myFoo = new ClassyFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
myFooList.push(myFoo);
});
} else {
testCase = (function () {
myFoo = new ClassyFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
});
}
time = new Date();
(amountOfTestCycles).times(function (/*idx, len, fct*/) {
testCase();
});
time = ((new Date()) - time);
print("time for " + amountOfTestCycles + " testcyles with" + ((isCaching) ? ("") : ("out")) + " [[ClassyFoo]] instance caching : " + time + " msec");
})(50000);
alert("... pause ... proceed ... ");
(function (amountOfTestCycles, isCaching) {
var testCase, time, myId, myFoo, myFooList = [];
if (isCaching) {
testCase = (function () {
myFoo = new FunkedFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
myFooList.push(myFoo);
});
} else {
testCase = (function () {
myFoo = new FunkedFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
});
}
time = new Date();
(amountOfTestCycles).times(function (/*idx, len, fct*/) {
testCase();
});
time = ((new Date()) - time);
print("time for " + amountOfTestCycles + " testcyles with" + ((isCaching) ? ("") : ("out")) + " [[FunkedFoo]] instance caching : " + time + " msec");
})(50000);
alert("... pause ... proceed ... ");
(function (amountOfTestCycles, isCaching) {
var testCase, time, myId, myFoo, myFooList = [];
if (isCaching) {
testCase = (function () {
myFoo = new ClassyFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
myFooList.push(myFoo);
});
} else {
testCase = (function () {
myFoo = new ClassyFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
});
}
time = new Date();
(amountOfTestCycles).times(function (/*idx, len, fct*/) {
testCase();
});
time = ((new Date()) - time);
print("time for " + amountOfTestCycles + " testcyles with" + ((isCaching) ? ("") : ("out")) + " [[ClassyFoo]] instance caching : " + time + " msec");
})(50000, true);
alert("... pause ... proceed ... ");
(function (amountOfTestCycles, isCaching) {
var testCase, time, myId, myFoo, myFooList = [];
if (isCaching) {
testCase = (function () {
myFoo = new FunkedFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
myFooList.push(myFoo);
});
} else {
testCase = (function () {
myFoo = new FunkedFoo(23);
myId = myFoo.getId();
myId = myFoo.showSpecialId();
});
}
time = new Date();
(amountOfTestCycles).times(function (/*idx, len, fct*/) {
testCase();
});
time = ((new Date()) - time);
print("time for " + amountOfTestCycles + " testcyles with" + ((isCaching) ? ("") : ("out")) + " [[FunkedFoo]] instance caching : " + time + " msec");
})(50000, true);
... klarheit schafft hier nur ein performance-test.
»»
ich beuge mich auch jedem urteil; wobei noch viel interessanter
waere, wie die unterschiedlichen scripting hosts dabei abschneiden.
lieber Timo, danke fuer die lektion.
so long - peterS. - pseliger@gmx.net