Orlok: Array.prototype

Hallo

Obwohl ich aufgrund eines anstehenden Umzugs und der Arbeit für’s Studium im Moment nur wenig Zeit für andere Aktivitäten habe, ist es mir dennoch gelungen, den Artikel im Wiki zu Array.prototype soweit fertigzustellen. Dabei habe ich unter anderem auch einiges zu der Problematik geschrieben, eigene Arraymethoden zu definieren, ohne dabei das Standardobjekt Array.prototype zu manipulieren.

Man könnte zu diesem Thema natürlich noch einiges mehr schreiben und die Beschreibungen der einzelnen Varianten könnten sicher noch detaillierter sein, aber aus Zeitgründen habe ich mich hierbei auf das Wesentliche beschränkt, zumal ich darüber hinaus auch nicht anderen, noch zu schreibenden Artikeln vorgreifen wollte, wie beispielsweise zum Thema Klassensyntax.

Der Artikel kann dessen ungeachtet sicherlich an der ein oder anderen Stelle noch etwas Feinschliff vertragen, wobei ich insbesondere bei den Codebeispielen noch Verbesserungspotential sehe, aber ich denke, im Prinzip kann man es erstmal so stehenlassen, zumal es ja noch genügend andere Baustellen gibt, deren Bearbeitung eine größere Priorität genießen sollte.

Negativ aufgefallen ist mir beim Schreiben übrigens die Syntaxhervorhebung im Wiki, bei der einige Schlüsselwörter, wie zum Beispiel class, extends oder super in einer abweichenden Farbe dargestellt werden, die meiner Meinung nach dazu geeignet ist, der Netzhaut oder wenigstens dem ästhetischen Empfinden schweren Schaden zuzufügen. – Vielleicht kann man dagegen ja etwas machen …

Viele Grüße,

Orlok

akzeptierte Antworten

  1. Hallo Orlok,

    Negativ aufgefallen ist mir beim Schreiben übrigens die Syntaxhervorhebung im Wiki, bei der einige Schlüsselwörter, wie zum Beispiel class, extends oder super in einer abweichenden Farbe dargestellt werden, die meiner Meinung nach dazu geeignet ist, der Netzhaut oder wenigstens dem ästhetischen Empfinden schweren Schaden zuzufügen.

    Ich weiß gar nicht, was du hast ;-)

    Bis demnächst
    Matthias

    --
    Dieses Forum nutzt Markdown. Im Wiki erhalten Sie Hilfe bei der Formatierung Ihrer Beiträge.
  2. Lieber Orlok,

    nehmen wir an, ich hätte für ein älteres Objekt selbst eine forEach-Methode gebastelt und sie über Array.prototype an meine Arrays geflanscht. Nun, da der Standard eine solche Methode vorsieht, wäre dieses Vorgehen insofern unproblematisch, als dass ich auf diese Manipulation von Array.prototype zugunsten der nun nativ vorhandenen Methode schlicht verzichten könnte. Die Reihenfolge der Parameter für die Callback-Funktion einmal außen vor.

    Nun ist es in der Tat aber so, dass ich diese Methode nicht forEach, sondern each genannt habe. Momentan stört sich das vielleicht nicht, weil andere Entwickler sich vielleicht mit dem Entwurf für ECMA-Script 6 beschäftigt haben, und ein eventuell vorausgreifendes Polyfill schon mit dem richtigen Bezeichner forEach erstellt haben - und mein each niemanden (mehr) juckt oder jucken wird.

    Für die Zukunft

    Wie baue ich mir ein eigenes Array (nennen wir es MyArray), das ebenso prototypisch von Array erbt, Änderungen an MyArray.prototype aber nur dort, nicht aber an Array.prototype vorgenommen werden?

    Liebe Grüße,

    Felix Riesterer.

    1. Wie baue ich mir ein eigenes Array (nennen wir es MyArray), das ebenso prototypisch von Array erbt, Änderungen an MyArray.prototype aber nur dort, nicht aber an Array.prototype vorgenommen werden?

      Damit beschäftigt sich der neue Text doch: Selbstdefinierte Methoden

      Alex

    2. Hallo Felix

      Irgendwie habe ich heute echt Probleme zu verstehen, was manche Leute mir sagen wollen. ;-)

      Nehmen wir an, ich hätte für ein älteres Objekt selbst eine forEach-Methode gebastelt und sie über Array.prototype an meine Arrays geflanscht.

      Du meinst, für ein älteres Projekt?

      Nun, da der Standard eine solche Methode vorsieht, wäre dieses Vorgehen insofern unproblematisch, als dass ich auf diese Manipulation von Array.prototype zugunsten der nun nativ vorhandenen Methode schlicht verzichten könnte.

      Wenn die Frage lautet, ob man die native forEach-Methode ohne Netz und doppelten Boden benutzen kann, dann lautet die Antwort: Ja. Die wird von allen relevanten Ausführungsumgebungen unterstützt.

      Die Reihenfolge der Parameter für die Callback-Funktion einmal außen vor.

      Nun ist es in der Tat aber so, dass ich diese Methode nicht forEach, sondern each genannt habe. Momentan stört sich das vielleicht nicht, weil andere Entwickler sich vielleicht mit dem Entwurf für ECMA-Script 6

      ECMAScript 5

      beschäftigt haben, und ein eventuell vorausgreifendes Polyfill schon mit dem richtigen Bezeichner forEach erstellt haben - und mein each niemanden (mehr) juckt oder jucken wird.

      Inwiefern deine Methode each tatsächlich jemanden juckt oder jucken wird, vermag ich natürlich nicht zu sagen, aber prinzipiell wäre das Vorgehen schon problematisch, denn wie du ja selbst sagst, handelt es sich bei deiner Methode nicht um ein standardkonformes Polyfill.

      Für die Zukunft

      Wie baue ich mir ein eigenes Array (nennen wir es MyArray), das ebenso prototypisch von Array erbt, Änderungen an MyArray.prototype aber nur dort, nicht aber an Array.prototype vorgenommen werden?

      Sorry, irgendwie stehe ich etwas auf dem Schlauch, aber wenn ich dich richtig verstehe, dann willst du in etwa sowas wie das hier …

      function MyArray ( ) {
        // create array
      }
      
      MyArray.prototype.method = function ( ) {
        // do something
      };
      
      const array = new MyArray;
      
      array.method( );
      

      … wobei der Prototyp von MyArray.prototype das Objekt Array.prototype sein soll, damit die erzeugten Instanzen sowohl die selbstdefinierten Methoden als auch die eingebauten Methoden erben, ohne dass dabei Array.prototype angefasst werden muss. Richtig?

      Wenn das deine Frage sein sollte, dann habe ich dazu im Abschnitt Selbstdefinierte Methoden meines Artikels ja schon einiges geschrieben. Aber ich kann gerne noch einige Punkte ergänzen, die dort vielleicht etwas zu kurz gekommen sind.

      Zum Beispiel könnte man erklären, warum die oben gezeigte Variante mit dem Konstruktor unter den genannten Voraussetzungen nicht funktionieren kann.

      MyArray.prototype = [ ];
      

      Dabei wäre zunächst zu erwähnen, dass es natürlich möglich ist, ein Array als prototypisches Objekt des Konstruktors anzulegen, sodass die von dieser Funktion erzeugten Instanzen sowohl die auf diesem Objekt definierten eigenen Methoden, als auch die Methoden von Array.prototype erben.

      const instance = new MyArray;
      
      console.log(Array.prototype.isPrototypeOf(instance)); // true
      
      console.log(Array.isArray(instance)); // false
      

      Aber das Objekt, das bei einem Aufruf via [[Construct]] erzeugt wird, in dessen Kontext die Funktion ausgeführt wird und das standardmäßig deren Rückgabewert ist, ist eben kein Array, sondern ein gewöhnliches Objekt.

      Das heißt, einem auf diese Weise erzeugten Objekt fehlt die besondere Semantik von Arrays, es besitzt also weder eine Eigenschaft length, noch das besondere Verhalten beim schreibenden Zugriff auf Objekteigenschaften, wie es in der internen Methode [[DefineOwnProperty]] bestimmt ist.

      function MyArray ( ) {
        return Array.from(arguments);
      }
      
      const instance = new MyArray;
      
      console.log(MyArray.prototype.isPrototypeOf(instance)); // false
      

      Wird in der Konstruktorfunktion hingegen ein anderes Objekt als Rückgabewert bestimmt, als das Objekt in dessen Kontext die Funktion ausgeführt wird, dann wird zwar dieses Objekt zurückgegeben, aber es wird dabei nicht der Prototyp verändert. Das heißt, man bekäme hierbei nur ein normales Array, ohne dass dessen Prototyp auf MyArray.prototype gesetzt würde.

      Es ist also egal wie man es dreht und wendet, man bekommt hier entweder nur ein ganz gewöhnliches Array, dessen interne Eigenschaft [[Prototype]] eine Referenz auf das Objekt Array.prototype enthält, oder aber ein Objekt, das zwar über die gewünschte Prototypenkette verfügt, das aber selbst kein exotisches Arrayobjekt ist.

      Wenn wir nun aus guten Gründen die Möglichkeit ignorieren, nach der Erzeugung eines Arrays ein Objekt in dessen Prototypenkette einzufügen, dann kann die gewünschte Funktionalität tatsächlich nur auf eine Weise implementiert werden, nämlich in Form einer von Array abgeleiteten Klasse.

      class MyArray extends Array {
        method ( ) {
          // do something
        }
      }
      
      const instance = new MyArray;
      
      console.log(Array.isArray(instance)); // true
      

      Hier haben wir also eine Klasse MyArray, die vom Konstruktor Array abgeleitet ist. Das heißt, die von dieser Klasse erzeugten Instanzen sind richtige Arrays, die sowohl die Methoden von MyArray.prototype als auch die Methoden von Array.prototype erben.

      console.log(MyArray.prototype.hasOwnProperty('method')); // true
      
      console.log(MyArray.prototype.propertyIsEnumerable('method')); // false
      

      Die im Körper der Klasse definierten Methoden werden dabei automatisch auf dem prototypischen Objekt der Funktion angelegt. Diese Form der Methodendefinition hat darüber hinaus den Vorteil, dass das interne Attribut [[Enumerable]] der Methode standardmäßig auf false gesetzt wird.

      class MyArray extends Array {
        constructor ( ) {
          super(...arguments);
          console.log(this.length);
        }
        method ( ) {
          // do something
        }
      }
      
      const array = new MyArray(2, 4, 8); // 3
      

      Optional kann innerhalb der Klasse die Pseudomethode constructor notiert werden, die im Prinzip wie ein gewöhnlicher Konstruktor funktioniert. Damit die Methode bei einer abgeleiteten Klasse aber auch im Kontext des Objektes ausgeführt wird, welches von der Superklasse erzeugt wird, hier also durch den Konstruktor Array, muss diese innerhalb der Methode mit dem Keyword super aufgerufen werden.

      Im Ergebnis ist hier also Array.prototype der Prototyp von MyArray.prototype und die erzeugten Instanzen sind native Arrays. Das heißt es können, vorzugsweise aber nicht zwingend innerhalb des Körpers der Klasse, beliebige Arraymethoden definiert werden, ohne dabei Array.prototype zu manipulieren.

      Für die Zunkunft ist das also das Mittel der Wahl, wenn es darum geht, eigene Methoden für Arrays zu definieren.

      Viele Grüße,

      Orlok

      1. Lieber Orlok,

        Du meinst, für ein älteres Projekt?

        ja, sorry. Ist mir beim Korrekturlesen durchgerutscht.

        class MyArray extends Array {
          method ( ) {
            // do something
          }
        }
        
        const instance = new MyArray;
        
        console.log(Array.isArray(instance)); // true
        

        AHA!!! Das kannte ich bisher nur von PHP und C#. Ist das jetzt ECMAScript 6? Bisher dachte ich, dass man in JavaScript keine Klassen definieren kann, dass aber class ein reserviertes Wort wäre. Mir leuchtet das Code-Beispiel sofort ein, zeigt es doch eine ganz klassische Vererbung von Klassen.

        Wenn man nun in JavaScript doch Klassen erstellt und erweitern kann, dann erübrigt sich meine Frage komplett, denn sie basiert auf der Annahme, dass man in JavaScript eben keine Klassen hat.

        class MyArray extends Array {
          constructor ( ) {
            super(...arguments);
            console.log(this.length);
          }
          method ( ) {
            // do something
          }
        }
        
        const array = new MyArray(2, 4, 8); // 3
        

        Optional kann innerhalb der Klasse die Pseudomethode constructor notiert werden

        Mensch, sogar die Methode __construct in PHP findet sich hier wieder!

        Liebe Grüße,

        Felix Riesterer.

        1. Tach!

          AHA!!! Das kannte ich bisher nur von PHP und C#. Ist das jetzt ECMAScript 6? Bisher dachte ich, dass man in JavaScript keine Klassen definieren kann, dass aber class ein reserviertes Wort wäre. Mir leuchtet das Code-Beispiel sofort ein, zeigt es doch eine ganz klassische Vererbung von Klassen.

          JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript.

          „Echte“ Klassen gibt es weiterhin nicht. Wenn du wissen möchtest, wie der Syntaxtic Sugar in das herkömmliche Konzept vermutlich übersetzt wird, solltest du mal einen Blick auf TypeScript werfen. Im Playground siehst du rechts die Übersetzung in klassisches Javascript. Es gibt weiterhin Unterschiede zwischen Typescript und Javascript, alle Feinheiten sind nicht in der jeweils anderen Sprache verfügbar, aber die wesentlichen Teile sind gleich.

          P.S.: Über das Select-Feld kann man ein paar vordefinierte Beispiele auswählen.

          dedlfix.

          1. Hallo dedlfix,

            Wenn du wissen möchtest, wie der Syntaxtic Sugar in das herkömmliche Konzept vermutlich übersetzt wird, solltest du mal einen Blick auf TypeScript werfen.

            Ja, TypeScript ist definitiv eine Empfehlung wert.

            Im Playground siehst du rechts die Übersetzung in klassisches Javascript.

            Ja, und da sieht man dann auch gleich, wie extends umgesetzt wird:

            var __extends = (this && this.__extends) || function (d, b) {
                for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
                function __() { this.constructor = d; }
                d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
            };
            

            Ich hätte es ähnlich umgesetzt. Mir fiele auch keine andere Methode ein, das zu bauen, deshalb war ich ein wenig erstaunt ob Orloks Empfehlung ;-)

            LG,
            CK

            1. Hallo Christian

              Ich hätte es ähnlich umgesetzt. Mir fiele auch keine andere Methode ein, das zu bauen, deshalb war ich ein wenig erstaunt ob Orloks Empfehlung ;-)

              Äh, warum genau? – Ich meine, dir ist schon klar, dass die gezeigte Variante nicht das gewünschte Ergebnis bringen wird, wenn es sich bei der Basisklasse um den Konstruktor Array handelt, oder?


              Nehmen wir mal dieses simple Beispiel, das valides ECMAScript 6 ist:

              class CustomArray extends Array {
                method ( ) {
                  // do something
                }
              }
              
              const instance = new CustomArray;
              
              console.log(Array.isArray(instance)); // true
              

              In TypeScript notiert und nach ECMAScript 5 übersetzt ergibt das:

              var __extends = (this && this.__extends) || function (d, b) {
                for (var p in b) {
                  if (b.hasOwnProperty(p)) {
                    d[p] = b[p];
                  }
                }
                function __() {
                  this.constructor = d;
                }
                d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
              };
              
              
              var CustomArray = (function (_super) {
                __extends(CustomArray, _super);
                function CustomArray() {
                  _super.apply(this, arguments);
                }
                CustomArray.prototype.method = function () {
                  // do something
                };
                return CustomArray;
              }(Array));
              
              
              var instance = new CustomArray;
              
              console.log(Array.isArray(instance)); // false
              

              Man beachte jeweils die letzte Zeile. ;-)


              Wenn wir jetzt den ganzen unwichtigen Kram weglassen und uns auf das Wesentliche konzentrieren, wie ich das in meiner ersten Antwort bereits versucht habe, dann bleiben diese Zeilen:

              function CustomArray() {
                _super.apply(this, arguments);
              }
              

              Hier wird also der Konstruktor Array im Kontext des Objektes aufgerufen, das durch den Aufruf des Konstruktors CustomArray automatisch erzeugt wurde. Da es dem Konstruktor Array aber herzlich egal ist, in welchem Kontext er aufgerufen wird, wird hier im Ergebnis einfach nur ein gewöhnliches Array erzeugt, das mangels Referenz von der Garbage Collection auch gleich wieder einkassiert wird. Zurückgegeben wird von CustomArray schließlich ein planes Objekt und kein Array.


              Wie dedlfix sagte, handelt es sich bei den in ECMAScript 6 eingeführten Klassen im Großen und Ganzen bloß um syntaktischen Zucker, aber eben nicht nur.

              class CustomArray extends Array {
                constructor ( ) {
                  super(...arguments);
                  console.log(Array.isArray(this));
                }
              }
              
              const instance = new CustomArray; // true
              

              Durch den expliziten oder impliziten Aufruf mittels super wird der Konstruktor der abgeleiteten Klasse im Kontext des Objektes ausgeführt, das durch den Konstruktorenaufruf der Basisklasse erzeugt wurde, hier also des Konstruktors Array.

              Im Umkehrschluss heißt das, dass nicht einfach der Konstruktor der Basisklasse als Funktion im Kontext des Objektes aufgerufen wird, das von dem Konstruktor der abgeleiteten Klasse erzeugt wurde, wie dies etwa in der TypeScript-Übersetzung geschieht.

              Wenn es also um selbstdefinierte Klassen beziehungsweise Konstruktoren geht, die plane Objekte erzeugen, dann besteht hier natürlich kein großer Unterschied, aber in Bezug auf eingebaute Objekttypen mit besonderer Semantik, wie es eben bei Arrays der Fall ist, wird hierdurch eine Funktionalität bereitgestellt, die es früher nicht gegeben hat.

              Denn hierdurch wird vom Konstruktor der Basisklasse ein Objekt erzeugt, dessen Prototyp das Objekt ist, welches in der Eigenschaft prototype der abgeleiteten Klasse hinterlegt ist. Das heißt, bezogen auf das Beispiel oben, wird ein exotisches Arrayobjekt erzeugt, dessen Prototyp CustomArray.prototype ist, und dies kann auf andere Weise nicht erreicht werden!

              function CustomArray ( ) {
                const array = Array.from(arguments);
                Object.setPrototypeOf(array, CustomArray.prototype);
                // array.__proto__ = CustomArray.prototype;
                return array;
              }
              
              CustomArray.prototype = [ ];
              
              const instance = CustomArray( );
              

              Sprich, wenn es um eingebaute, spezifische Objekttypen geht, kann das Einfügen eines Objektes in die Prototypenkette ohne die neue Syntax immer nur nachträglich geschehen, nachdem das entsprechende Instanzobjekt erzeugt wurde, was sich allerdings ziemlich negativ auf die Performance auswirkt.

              Sofern man also Array.prototype nicht manipulieren will, man die Methoden auch nicht auf jeder einzelnen Instanz definieren möchte, man die nachträgliche Injektion in die Prototypenkette vermeiden will und man die Funktionalität auch nicht auf andere Weise bereitzustellen gedenkt, bleibt also ganz einfach nichts anderes übrig, als sich der Syntax für abgeleitete Klassen zu bedienen. ;-)

              Viele Grüße,

              Orlok

              1. Tach!

                Nehmen wir mal dieses simple Beispiel, das valides ECMAScript 6 ist: [...]

                In TypeScript notiert und nach ECMAScript 5 übersetzt ergibt das:

                Äpfel und Birnen miteinander zu vergleichen ist nicht hilfreich, wenn Gleichheit das Ergebnis sein soll. Gäbe es denn für ES5 die Möglichkeit, das so umzusetzen, dass dein (wie mir scheint akademisches, aber was weiß ich schon) Beispiel das gewünschte Ergebnis liefert? Jedenfalls kann die aktuelle Versionen von Typescript auch nach ES6 übersetzen, dann ist das Ergebnis auch Code mit syntaktischen Zucker und die von dir angesprochenen Zusatzstoffe sind automatisch enthalten.

                dedlfix.

                1. Hallo dedlfix

                  Äpfel und Birnen miteinander zu vergleichen ist nicht hilfreich, wenn Gleichheit das Ergebnis sein soll. Gäbe es denn für ES5 die Möglichkeit, das so umzusetzen, dass dein (wie mir scheint akademisches, aber was weiß ich schon) Beispiel das gewünschte Ergebnis liefert? Jedenfalls kann die aktuelle Versionen von Typescript auch nach ES6 übersetzen, dann ist das Ergebnis auch Code mit syntaktischen Zucker und die von dir angesprochenen Zusatzstoffe sind automatisch enthalten.

                  Es ging mir hier nicht um einen Vergleich von TypeScript und ECMAScript. Ich habe lediglich die Aussage von Christian aufgegriffen, wonach er eine von Array abgeleitete Klasse so ähnlich umgesetzt hätte wie in dem Code, zu dem das TypeScript-Beispiel übersetzt wurde. Jedenfalls habe ich ihn so verstanden, zumal er sich dabei auf meinen Beitrag bezogen hatte. Dementsprechend ging es mir nur darum zu zeigen, dass eine solche Implementierung nicht funktionieren kann.

                  Möglicherweise habe ich Christian aber auch falsch verstanden und er bezog sich nicht auf den konkreten Fall, der Gegenstand meiner Antwort an Felix war, sondern er meinte die Erstellung von abgeleiteten Klassen ganz allgemein. In diesem Fall wäre mein Beitrag vermutlich überflüssig.

                  Der Ursprung der ganzen Diskussion liegt übrigens darin, dass ich, obwohl ich tatsächlich nur über akademisches Wissen verfüge und nicht auf zwanzig Jahre Entwicklertätigkeit zurückblicken kann, es dennoch für nötig befunden habe, in meinem Artikel der Frage nachzugehen, wie man eigene Methoden für Arrays definieren kann, ohne das Standardobjekt Array.prototype zu manipulieren, welches sich alle eingebundenen Programme und möglicherweise beteiligten Entwickler teilen. Ich habe also versucht zu erklären, warum die Veränderung von Standardobjekten, von Polyfills einmal abgesehen, eine schlechte Praxis ist, habe das Risiko von Namenskonflikten benannt und versucht Alternativen aufzuzeigen.

                  Dabei bin ich zu dem Ergebnis gekommen, dass die selbstdefinierten Methoden idealerweise auf einem prototypischen Objekt angelegt werden sollten, welches in der Prototypenkette zwischen den Arrays und dem Objekt Array.prototype einzufügen ist, sodass die Arrays sowohl die nativen Methoden, als auch die selbstdefinierten Methoden erben, ohne dass Array.prototype manipuliert wird und ohne die Methoden auf jeder einzelnen Instanz anlegen zu müssen, oder die Funktionalität anderswohin zu delegieren.

                  Diese Lösung ist allerdings ohne die Syntax mit class und extends nicht zu verwirklichen, es sei denn, man fügt das prototypische Objekt mit den selbstdefinierten Methoden nach der Erzeugung der Instanzen in deren zu diesem Zeitpunkt bereits etablierte Prototypenkette ein, was aus Gründen der Performanz im Allgemeinen nicht zu empfehlen ist. Womit deine Frage wohl beantwortet wäre.

                  Nicht beantwortet ist damit aber die Frage, was denn nun als best practice für die Definition eigener Arraymethoden zu empfehlen sei. Hier bin ich zu dem Ergebnis gekommen, dass ich meinen Artikel nicht für die Leser von gestern, sondern für die Leser von morgen schreibe und entsprechend habe ich die Methodendefinition innerhalb einer von Array abgeleiteten Klasse empfohlen, wie es nach dem aktuellen Standard der Sprache möglich ist. Darüber hinaus hatte Felix in diesem Thread gefragt, wie in einer solchen Situation in Zukunft zu verfahren sei, also habe ich hier dieselbe Antwort gegeben.

                  Tempus fugit, aber wie ist die Situation heute? Die Situation ist, dass die Klassensyntax, wie sie in der sechsten Edition der Sprache standardisiert wurde, noch nicht von allen Ausführungsumgebungen unterstützt wird, die für ein aktuelles Projekt von Bedeutung sind. Alternative Lösungsmöglichkeiten können also zumindest für den Moment nicht einfach ausgeblendet werden. Die Frage ist also zur Zeit noch, was mache ich, wenn ich keine von Array abgeleitete Klasse erstellen kann?

                  Diese Frage ist nicht leicht zu beantworten. Nicht nur das. Ich denke, sie ist pauschal überhaupt nicht zu beantworten, denn jede mögliche Alternative geht mit ganz eigenen Nachteilen einher, die unter verschiedenen Voraussetzungen unterschiedlich ins Gewicht fallen. Welche Vorgehensweise zu empfehlen ist, hängt also ganz wesentlich von dem konkreten Projekt ab, um das es sich handelt, und Ratschläge können hier dementsprechend meiner Ansicht nach nur in der Form gegeben werden, die verschiedenen Alternativen zu benennen und ihre Vor- und Nachteile aufzuzeigen.

                  Da ich diese Situation also nicht ignorieren konnte, habe ich in meinem Artikel mehrere Möglichkeiten präsentiert, wie Methoden definiert werden können, ohne dabei im globalen Namensraum seine Spuren zu hinterlassen und ohne sich dabei der Syntax mit class und extends zu bedienen.

                  Hier bereits genannt habe ich die Alternativen, die Methoden auf den Instanzen selbst zu definieren, oder ein Objekt mit den Methoden nach Erzeugung der Instanzen in deren Prototypenkette zu injizieren. Erstgenanntes geht mit dem Nachteil des erhöhten Speicherverbrauchs einher, letztgenanntes mit mehr oder weniger erheblichem zeitlichen Mehraufwand bei den meisten auf dem Objekt ausgeführten Operationen. Welche der beiden Varianten kostspieliger ist, kann man ohne entsprechende Tests mit einem konkreten Programm wohl nicht beantworten. Jedenfalls kann ich es nicht.

                  Eine weitere Möglichkeit, potentielle Namenskonflikte und dadurch verursachte Fehler zu vermeiden, könnte natürlich darin liegen, die Methoden zwar auf Array.prototype zu definieren, ihnen aber derart kryptische oder spezielle Namen zu geben, dass eine mehrfache Definition unwahrscheinlich ist. Das wird womöglich in der Mehrzahl der Fälle eine hinreichend sichere Lösung sein, aber ein Risiko besteht hier dennoch und unter Umständen wird dabei die Les- und Wartbarkeit des Codes beeinträchtigt.

                  Weiterhin können die Methoden so definiert werden, dass sie nicht direkt auf einem Array operieren, sondern sie zunächst ein Array referenzieren, das in einem gewöhnlichen Objekt gekapselt ist, über dessen Prototyp die Methoden vererbt werden. Im Gegensatz zu anderen Formen der Delegation würde so eine Lösung noch halbwegs semantischen Code ergeben. Der direkte Zugriff kann hierbei aber natürlich nur über einen entsprechenden Elementausdruck erfolgen, gegebenenfalls über zu diesem Zweck definierte Methoden, was alles andere als elegant ist.

                  Abenteuerlustige Programmierer könnten darüber hinaus, zumindest in Browsern, auch einen iframe erstellen und mithin ein neues, frisches Set Standardobjekte, dessen Konstruktor Array und dessen prototypisches Objekt man exklusiv für sich beanspruchen könnte. Performant ist allerdings anders. Zudem hätte man den Nachteil, dass Operationen, die sich auf die Prototypenkette der auf diese Weise erstellten Arrays beziehen, unter Umständen zu unerwünschten Ergebnissen kommen. Eine solche Vorgehensweise kann demnach wohl kaum als ernsthafte Alternative in Erwägung gezogen werden.

                  Die letzte Möglichkeit die ich hier sehe, führt uns wieder an den Anfang der Überlegungen zurück, beziehungsweise zum Ursprung des Problems, nämlich dass es in ECMAScript 5 nur möglich ist, entweder ein Array zu erstellen, dessen Prototyp Array.prototype ist, oder aber ein gewöhnliches Objekt, dem die spezielle Semantik von Arrays fehlt, das aber über die gewünschte Prototypenkette verfügt.

                  Da nun Getter und Setter bereits seit ECMAScript 5 Teil des Standards sind und die Kompatibilität diesbezüglich gegeben sein dürfte, wäre es möglich statt native Arrays zu verwenden, deren Semantik auf planen Objekten nachzubauen. Das heißt, man könnte das besondere Verhalten hinsichtlich der Eigenschaft length und solcher Eigenschaften, deren Name ein numerischer Index ist, nachbilden.

                  Das wäre tatsächlich ohne allzu großen Aufwand möglich, allerdings könnte man hierbei nicht jedes Detail berücksichtigen, sodass darüber hinaus auch die ein oder andere native Arraymethode nachgebaut werden müsste. Ein solches Vorgehen wäre wohl nur in Ausnahmefällen anzuraten.

                  Weitere Lösungsmöglichkeiten fallen mir ehrlich gesagt nicht ein. Aber vielleicht müssen sie das ja auch nicht, weil das Problem rein akademisch ist. Womöglich habe ich beim Schreiben des Artikels und der Beiträge in diesem Thread einfach nur meine Zeit verschwendet, und die der Leser. Ob dem so ist, kann mir vielleicht jemand beantworten, der über mehr Praxiserfahrung verfügt als ich.

                  Gruß,

                  Orlok

                  1. Tach!

                    Äpfel und Birnen miteinander zu vergleichen ist nicht hilfreich, wenn Gleichheit das Ergebnis sein soll. Gäbe es denn für ES5 die Möglichkeit, das so umzusetzen, dass dein (wie mir scheint akademisches, aber was weiß ich schon) Beispiel das gewünschte Ergebnis liefert? Jedenfalls kann die aktuelle Versionen von Typescript auch nach ES6 übersetzen, dann ist das Ergebnis auch Code mit syntaktischen Zucker und die von dir angesprochenen Zusatzstoffe sind automatisch enthalten.

                    Es ging mir hier nicht um einen Vergleich von TypeScript und ECMAScript. Ich habe lediglich die Aussage von Christian aufgegriffen, wonach er eine von Array abgeleitete Klasse so ähnlich umgesetzt hätte wie in dem Code, zu dem das TypeScript-Beispiel übersetzt wurde. Jedenfalls habe ich ihn so verstanden, zumal er sich dabei auf meinen Beitrag bezogen hatte. Dementsprechend ging es mir nur darum zu zeigen, dass eine solche Implementierung nicht funktionieren kann.

                    Machst du gerade das "nicht funktionieren kann" an deinem einen Beispiel fest? Wenn ich dieses spezielle Möglichkeit nicht brauche, dann sehe ich ein völliges Funktionieren bezogen auf meine Anwendungsfälle (akademisch versus praktisch). Wenn ich mit Browsern / Javascript-Implementationen arbeiten muss, die ES5 aber noch kein ES6 können, dann ist es mir ziemlich egal, was das extends in ES6 noch so alles zusätzlich kann und dass das Typescript selbstverständlich nicht in der Lage ist, diese Features bieten zu können. Mir muss als Typescript-Benutzer klar sein, dass es nicht zaubern kann. Es gibt auch einige Sprachfeatures, die je nachdem, welches Target (ES5, ES6, ...) ich angebe, nicht zur Verfügung stehen (wie let bei ES5) oder die prinzipbedingt nur der Transpiler anmeckern kann, die aber, wenn ich oder andere Bibliotheken mit plain old Javascript weiterprogrammieren, umgangen werden (Zugriffsmodifikator private, starke Typisierung). Nichtsdestotrotz kann Typescript ein ziemlicher Gewinn für das Erstellen von Anwendungen sein. Vor allem auch dann, wenn man Features verwendet, die in der durch die Browser vorgegebenen, lediglich verwendbaren Javascript-Version ziemlich verbose zu implementieren sind, während Typescript eine verständliche Einzeilen-Syntax bietet (Getter/Setter).

                    Möglicherweise habe ich Christian aber auch falsch verstanden und er bezog sich nicht auf den konkreten Fall, der Gegenstand meiner Antwort an Felix war, sondern er meinte die Erstellung von abgeleiteten Klassen ganz allgemein. In diesem Fall wäre mein Beitrag vermutlich überflüssig.

                    Er sagte ja auch "ähnlich" und das enthält keinerlei Aussage darüber, ob bestimmte Features anderer Versionen von Javascript haarklein unterstützt werden oder vielleicht doch.

                    Der Ursprung der ganzen Diskussion liegt übrigens darin, dass ich, obwohl ich tatsächlich nur über akademisches Wissen verfüge und nicht auf zwanzig Jahre Entwicklertätigkeit zurückblicken kann, es dennoch für nötig befunden habe, in meinem Artikel der Frage nachzugehen, wie man eigene Methoden für Arrays definieren kann, ohne das Standardobjekt Array.prototype zu manipulieren,

                    Auf das Thema habe ich mich gar nicht bezogen. X Jahre Entwicklertätigkeit ist auch kein Argument, auf das ich einen Pfifferling geben würde, um daraus eine Aussage zur Qualität der Erzeugnisse oder gar des Wissens abzuleiten.

                    Dabei bin ich zu dem Ergebnis gekommen, dass die selbstdefinierten Methoden idealerweise auf einem prototypischen Objekt angelegt werden sollten, welches in der Prototypenkette zwischen den Arrays und dem Objekt Array.prototype einzufügen ist, sodass die Arrays sowohl die nativen Methoden, als auch die selbstdefinierten Methoden erben, ohne dass Array.prototype manipuliert wird und ohne die Methoden auf jeder einzelnen Instanz anlegen zu müssen, oder die Funktionalität anderswohin zu delegieren.

                    Am Ende muss das eigene Projekt gemäß der Aufgabenstellung arbeiten. Das was theoretisch alles möglich ist, braucht man meist alles gar nicht. Kein Kunde oder Anwender (wenn es kein geschäftlicher Auftrag war) schaut nach, wo man Standard-Objekte erweitert hat, wenn man sowas braucht oder ob man sich einfach ohne groß hinzuschauen das Polyfill aus dem MDN kopiert hat. Als Framework-Entwickler muss man auf Feinheiten der Sprache vermutlich eher achten als wenn man lediglich eine Anwendung für einen konkreten Zweck schreibt. Unwägbarkeiten wegen Browserimplementationen umgeht man, indem man mit einer konservativeren Herangehensweise weiterarbeitet.

                    Diese Lösung ist allerdings ohne die Syntax mit class und extends nicht zu verwirklichen, es sei denn, man fügt das prototypische Objekt mit den selbstdefinierten Methoden nach der Erzeugung der Instanzen in deren zu diesem Zeitpunkt bereits etablierte Prototypenkette ein, was aus Gründen der Performanz im Allgemeinen nicht zu empfehlen ist. Womit deine Frage wohl beantwortet wäre.

                    Also, das Feature geht sowieso nicht in ES5. Wenn ich das zufälligerweise brauchen würde, muss ich für das eigentliche Problem einen anderen Weg suchen, sodass sich die Notwendigkeit erübrigt. Nicht der Nachbau steht mir im Weg, sondern die für die praktische Anwendung generell nicht vorhandene Möglichkeit. Da muss man erst noch genügend Zeit ins Land streichen lassen, bevor man guten Gewissens die begrenzte Lauffähigkeit moderner Versionen auf alten Implementationen vernachlässigen kann. Wenn es dann soweit ist, steht man als Entwickler wieder an einem vergleichbaren Punkt. Die Sprache hat sich weiterentwickelt, die Implementation sind noch nicht völlig fertig und die Verbreitung moderner Browser nicht flächendeckend abgeschlossen (es sei denn, alle Nicht-Evergreen-Browser sind ausgestorben).

                    Nicht beantwortet ist damit aber die Frage, was denn nun als best practice für die Definition eigener Arraymethoden zu empfehlen sei. Hier bin ich zu dem Ergebnis gekommen, dass ich meinen Artikel nicht für die Leser von gestern, sondern für die Leser von morgen schreibe

                    Der Anwender von heute muss aber auch verwertbares Wissen im Wiki finden. Aber das scheinst du ja gemäß nachfolgenden, nicht mehr von mir zitierten Aussagen nicht unbeachtet zu lassen.

                    Weitere Lösungsmöglichkeiten fallen mir ehrlich gesagt nicht ein. Aber vielleicht müssen sie das ja auch nicht, weil das Problem rein akademisch ist. Womöglich habe ich beim Schreiben des Artikels und der Beiträge in diesem Thread einfach nur meine Zeit verschwendet, und die der Leser. Ob dem so ist, kann mir vielleicht jemand beantworten, der über mehr Praxiserfahrung verfügt als ich.

                    Es muss auch Leute geben, die Zeit haben, darüber nachzudenken, was theoretisch möglich ist. Der gehetzte Praktiker hingegen kopiert sich einen Polyfill oder umgeht das spezielle Problem mit einer alternativen Lösung für sein eigentliches Problem.

                    dedlfix.

          2. Hi

            „Echte“ Klassen gibt es weiterhin nicht.

            Was macht Klassen denn zu echten Klassen? Welches Feature fehlt den Klassen in JavaScript um "echt" zu sein? ;-)

            Wenn du wissen möchtest, wie der Syntaxtic Sugar in das herkömmliche Konzept vermutlich übersetzt wird, solltest du mal einen Blick auf TypeScript werfen.

            Oder Babel

            Alex

            1. Tach!

              „Echte“ Klassen gibt es weiterhin nicht.

              Was macht Klassen denn zu echten Klassen? Welches Feature fehlt den Klassen in JavaScript um "echt" zu sein? ;-)

              Damit ist gemeint, dass im Hintergrund immer noch das für Javascript typische Gefummel mit den Prototypen und so weiter stattfindet. An der Oberfläche mag man eine ganze Menge der anderswo typischen OOP haben (Zugriffsmodifikatoren zum Beispiel nicht), aber unter der Haube hat man teilweise immer noch mit Krücken zu kämpfen. Getter und Setter auf herkömmliche Weise zu definieren ist zum Beispiel eine Katastrophe.

              dedlfix.