Rolf B: Warum bei Prototype-Inheritance die Konstruktor-Funktion von Parentclass zu Childclass wechseln???

Beitrag lesen

Hallo MB,

"objektorientiert" und "objektbasierend" - uff, das musste ich jetzt erstmal nachschlagen. Demnach ist "objektbasierende" Programmierung, wenn Du mit den Objekten hantierst, die JavaScript und Browser mitbringen, aber keine eigenen erstellst.

Sobald Du deinen Code selbst in Objekte steckst und sogar Vererbung verwendest, bist Du objektorientiert. Sagt Tante Wiki. Demnach ist es für OO vs OB total wurscht, ob Du new Foo() oder Object.create(Foo.prototype) verwendest. Es ist beides OO.

Der new Operator und das class Schlüsselwort - da hat der Tutor recht, das ist Syntaxzucker. Er ist aber lecker. Diese beiden JS-Beispiele tun äußerlich exakt das Gleiche:

let a = new Foo("bar", 42);

vs

let a = Object.create(Foo.prototype);

Foo.call(a, "bar", 42);
// oder
Foo.apply(a, [ "bar", 42 ] );

Der Zucker enthält aber auch ein paar Körnchen Salz (das hebt bekanntlich den Geschmack). Gerade bei mehrstufiger Vererbung hast Du viel Handarbeit, die Dir die class-Syntax und der new Operator abnehmen.

class Parent {
   constructor(x) {
      this.x = x;
   }
   foo(t) {
      return t + this.x;
   }
}

class Child extends Parent {
   constructor(x,q) {
      super(x);
      this.q = q;
   }
   bar(u) {
      return u * this.q + this.x;
   }
}

let c = new Child(42, 17);

Das ist ohne class Syntax eine Menge mehr Arbeit. Vor allem musst Du die Programmiermuster exakt einhalten, um die Objekte sauber hinzubekommen, und ich kann mir nicht vorstellen, dass von Hand erstellte prototypische Vererbung - außer in Sonderfällen - performanter ist. Und wenn, dann höchstens um ein paar Nanosekunden.

Prototypen haben den Vorteil, dass man mal eben so ein Objekt als Vererbungsgrundlage nehmen kann, ohne Klassen definieren zu müssen. Das ermöglicht interessante Techniken zur Komposition von Bausteinen zur Laufzeit, die mit Klassen deutlich umständlicher sind (falls Du das Entwurfsmuster-Buch der Gang of Four mal gelesen hat: denke an das Dekorierer-Muster).

Das schöne an JS ist aber auch, dass Du fallweise entscheiden kannst, ob Du eine Klasse notierst oder mit Object.create arbeitest, so dass Du nach Bedarf die bessere Lösung auswählen kannst.

ich hab's befürchtet, dass das n Performance-Problemchen werden würde, wenn man tausende Objekte erzeugt. Die new-Funktion schneidet besser ab?

Nein, das ist kein Problem von new. Sondern davon, wieviel Aufwand Du in die Erzeugung eines Objekts steckst. Deswegen schrieb ich ja, dass man im Konstruktor (oder bei der Objektinitialisierung im Prototyp-Verfahren) keine Methoden an das neue Objekt zuweisen soll, sondern Methoden nach aller Möglichkeit vom Prototypen erben soll.

Um bei deinem Abschlussbeispiel zu bleiben:

const Foo = function(bar) {
   this.bar = bar;
}
Foo.prototype.getBar = function() { return this.bar; }

ist so okay. Der getter wird vom Prototypen geerbt. Man könnte es aber auch so machen:

const Foo = function(bar) {
   let _bar = bar
   this.getBar = function() { return _bar; }
   this.setBar = function(b) { _bar = b; }
}

Auf den ersten Blick ist das großartig: Ich habe private Eigenschaften eines Projekts erfunden! Ich kann auf _bar nur über getter und setter zugreifen. Aber selbst dieser simple Konstruktor läuft doppelt so lange wie dies hier:

class Foo {
   #bar;
   get bar() { return this.#bar; }
   set bar(b) { this.+bar = b; }
}

Das ist die volle, zahnerweichende Zuckerdröhnung: class-Syntax, private Property, getter und setter. Und spätestens bei private-Eigenschaften (das # vor dem bar) ist die Class-syntax unverzichtbar, denn die sind anderweit nicht verfügbar, bestenfalls unvollständig simulierbar.

Und wenn Du die echte Property-Syntax ohne private properties simulieren willst, geht's auf Faktor 10 hoch:

function Foo() {
   let bar;
   Object.defineProperties(this,  {
      bar: { get: function() { return bar; },
             set: function(b) { bar = b; } } } );
}

Kann man so machen, erzeugt auch zuverlässig ein Property 'bar' mit einem privaten Backing-Feld - aber generiert auch eben zwei Funktionskontexte pro Konstruktoraufruf, was Zeit und Speicher kostet.

Wobei "doppelt so lange" ebenfalls mit einer Prise Salz zu genießen ist: Es sind auf meinem PC 300 Nanosekunden mit Class-Syntax, was dann auf 600 Nanosekunden oder 3 Mikrosekunden steigt. Man muss schon ziemlich viele Objekte erzeugen, bevor das weh tut.

Rolf

--
sumpsi - posui - obstruxi