Rolf b: "Vererbung"

Beitrag lesen

Der wesentliche Unterschied zwischen Java und Javascript ist hier, dass Methoden in Java grundsätzlich an der Klasse hängen. Ich weiß nicht GENAU, wie Java das unter der Haube deichselt, aber es sollte für jede Klasse eine VFT (virtual function table) geben, worin sich pro Methode ein Zeiger auf die Implementierung findet, und die VFT einer abgeleiteten Klasse ist die Kopie der VFT ihrer Superklasse, verlängert um die eigenen Methoden (weshalb mehrfache Vererbung in Java verboten ist; C++ bricht sich einige Register aus dem Prozessor um die VFTs bei mehrfacher Vererbung im Griff zu halten). Ein super-Aufruf sorgt einfach dafür, dass nicht die VFT der eigenen Klasse, sondern die VFT der Superklasse verwendet wird.

Um das zu übertragen, könnte man formulieren: In JavaScript hat jedes Objekt seine eigene VFT, nämlich die Function-Properties des Objekts, und erbt weitere VFTs über die Prototypenkette.

Dadurch, dass Du B.get und B.set im Konstruktor ans Objekt hängst (um das private b zu kapseln), und dann in C() eine Zuweisung this.set = ... machst, überschreibst Du den VFT-Bereich, der eigentlich der Superklasse gehört. Daraus resultieren Deine Schwierigkeiten.

Lösungsmöglichkeiten:

  1. Du verlegst B.get und B.set an den Prototypen von B.

    Nachteil: Du musst Dir etwas anderes einfallen lassen, um var b private zu bekommen. Hier kann Dir das Module-Pattern helfen und ein Symbol-Objekt, um aus b ein echtes Property zu machen, das aber nur mit Kenntnis des Symbols erreichbar ist (na gut, ohne Kenntnis des Symbols auch, aber das braucht dann schon die Brechstange). Und weil das Symbol Teil des IIFE-Scopes ist, kommt nur der Code heran, der innerhalb der IIFE steht.

    Damit hast Du die Möglichkeit, in C einen Aufruf wie B.prototype.set.call(this, val) zu machen. Kleiner Happen am Rande ist, dass ich die Superklasse als Parameter in die IIFE hineingebe, die den Konstruktor von B einschließt, und so innerhalb der IIFE keine Referenz auf einen externen Scope brauche.

    Vorteil des Module-Patterns ist, dass Du die Funktionsobjekte für get und set nur einmal bildest und nicht pro Objektkonstruktion. Und du hängst nicht den Scope-Kontext des Konstruktoraufrufs an jede Instanz dieser Funktionen. Falls Du viele B oder C Objekte erzeugst, ist das durchaus Laufzeit- und Speicherrelevant.

B = (function(baseClass) {
   let bKey = new Symbol();
   let BFactory = function() {
      baseClass.call(this);
      this[bKey] = 0;
   }
   BFactory.prototype = Object.create(baseClass.prototype);
   BFactory.prototype.get = function() { return this[bKey]; };
   BFactory.prototype.set = function(b) { this[bKey] = b; };

   return BFactory;
})(A);
  1. (das hier ist Nummer 2, das Forum macht eine 1 draus...) Ich vermute, dass folgende Konstruktion von C ebenfalls funktionieren sollte. Dann kannst Du B unverändert lassen.
function C() {
   B.call(this);
   let super_set = this.set;
   this.set = function(val) {
      if(val > 10) throw new Error("error");
      super_set(val);
   }
}
C.prototype = Object.create(B.prototype);

Aber wie gesagt - wenn Du viele Objekte erzeugst, ist das Module-Pattern ökonomischer.

Rolf