Reinhard: Private Klassen-Eigenschaften

Hey,

ich arbeite an einem kleinen Tool und mir wird gerade klar, wie doof das ist, dass es in JS keine richtige Privatisierung gibt. Folgendes Beispiel:

(function() {
    var myClass = function(msg) {
        this.msg = msg;
    };
    myClass.prototype.alert = function() {
        window.alert(reverse1(this));
        window.alert(reverse2.call(this));
    };
    
    var reverse1 = function(myC) {
        return myC.msg.split('').reverse().join('');
    };
    var reverse2 = function() {
        return this.msg.split('').reverse().join('');
    };
    
    var m = new myClass('hello world');
    m.alert();
})();

Sehe ich das richtig, dass eine „Privatisierung“ der Methode reverse nur durch die oben gezeigten Varianten zu erreichen ist? Wenn Ja, welche ist zu bevorzugen? Wenn Nein, wie geht es besser?

Reinhard

  1. ich arbeite an einem kleinen Tool und mir wird gerade klar, wie doof das ist, dass es in JS keine richtige Privatisierung gibt.

    Richtig, JavaScript kennt keine Gültigkeitsbeschränkungen auf Klassen- oder Objekt-Ebene. Datenkapselung erfolgt stattdessen durch den lexikalischen Scope. Für Programmierer mit klassischer objektorientierter Vorbelastung ist das gewöhnungsbedürftig, war es für mich anfangs auch. Aber mit der Zeit habe ich diese Trennung zwischen Datenkapselung und Objektorientierung wirklich schätzen gelernt. Es ist eine sinnvolle konzeptionelle Unterscheidung. Inzwischen kommt mit sogar die Vermengung von Datenkapselung und Objektorientierung, wie zum Beispiel in Java, ziemlich verkorkst vor.

    Sehe ich das richtig, dass eine „Privatisierung“ der Methode reverse nur durch die oben gezeigten Varianten zu erreichen ist? Wenn Ja, welche ist zu bevorzugen? Wenn Nein, wie geht es besser?

    In modernem JavaScript würdest du vermutlich Symbols verwenden wollen. Symbols sind neben Strings, die einzigen Datentypen, die als Schlüssel für Objekteigenschaften verwendet werden können. Anders als Strings haben sie die Eigenschaft einzigartig zu sein: Jeder Aufruf von "Symbol()" erzeugt ein neues Symbol, das von allen anderen Symbols verschieden ist. Sie führen kein neues Konzept für Datenkapselung ein, aber der Programmierer kann sie in Verbindung mit dem lexikalischen Scope zum Beispiel für private Member-Variablen nutzen.

    const privateLog = Symbol();
    export class MyClass {
      [privateLog](msg) {
         console.log(msg);
      },
      publicLog (msg) {
         this[privateLog](msg);
      }
    }
    

    Code, der diese Klasse importiert, erhält keinen Zugriff auf die private Methode, weil er das Symbol "privateLog" nicht kennt, weil es von dem Modul nicht exportiert wird [1]. Das ist ein sehr einfaches Beispiel, in der Praxis kann ein Programmierer die Granularität der Datenkapselung sehr viel freier nach seinen eigenen Wünschen gestalten und ist nicht auf bestimmte Berechtigungsstufen wie "private", "protected" und "public" beschränkt.


    1. Es gibt theoretisch eine globale Registry, in der der importierende Code, das Symbol nachschlagen könnte. Aber die Gefahr von versehentlicher Kollision ist damit gebannt. ↩︎

    1. Hey,

      Sehe ich das richtig, dass eine „Privatisierung“ der Methode reverse nur durch die oben gezeigten Varianten zu erreichen ist? Wenn Ja, welche ist zu bevorzugen? Wenn Nein, wie geht es besser?

      In modernem JavaScript würdest du vermutlich Symbols verwenden wollen. Symbols sind neben Strings, die einzigen Datentypen, die als Schlüssel für Objekteigenschaften verwendet werden können. Anders als Strings haben sie die Eigenschaft einzigartig zu sein: Jeder Aufruf von "Symbol()" erzeugt ein neues Symbol, das von allen anderen Symbols verschieden ist. Sie führen kein neues Konzept für Datenkapselung ein, aber der Programmierer kann sie in Verbindung mit dem lexikalischen Scope zum Beispiel für private Member-Variablen nutzen.

      const privateLog = Symbol();
      export MyClass {
        [privateLog](msg) {
           console.log(msg);
        },
        publicLog (msg) {
           this[privateLog](msg);
        }
      }
      

      Ich weiß nicht, wie „private Member-Variablen“ definiert sind, aber vollkommen private sind auch Symbols nicht:

      var expo = (function() {
          var reverseSym = Symbol();
          var myClass = function(msg) {
              this.msg = msg;
          };
          myClass.prototype.alert = function() {
              window.alert(this[reverseSym]());
          };
          myClass.prototype[reverseSym] = function() {
              return this.msg.split('').reverse().join('');
          };
          return myClass;
      })();
      var sym = Object.getOwnPropertySymbols(expo.prototype)[0];
      var reversed = expo.prototype[sym].call({ msg : 'my hello world' });
      

      Aber in gewisser Weise hast du schon recht. Dafür ist schon recht viel Know-How nötig, sodass man davon ausgehen kann, dass niemand „versehentlich“ eine Funktion aufruft, die er eigentlich nicht aufrufen können sollte. Sogesehen sind Symbols eine – in objektorientierter Hinsicht – elegante Alternative.

      Code, der diese Klasse importiert, erhält keinen Zugriff auf die private Methode, weil er das Symbol "privateLog" nicht kennt, weil es von dem Modul nicht exportiert wird.

      Object.getOwnPropertySymbols(obj) macht's möglich, s.o.

      Reinhard

      1. Tach!

        Ich weiß nicht, wie „private Member-Variablen“ definiert sind, aber vollkommen private sind auch Symbols nicht: [...] Dafür ist schon recht viel Know-How nötig, sodass man davon ausgehen kann, dass niemand „versehentlich“ eine Funktion aufruft, die er eigentlich nicht aufrufen können sollte.

        Wovor hast du eigentlich Angst/Bedenken/wasauchimmer? Wenn du Javascript-Code für andere schreibst, läuft der außerhalb deines Einflussbereiches und ist obendrein auch nicht vor Manipulationen und Einblicken geschützt. Im "Notfall" kopiert man sich den Code, korrigiert das ungewünschte Verhalten heraus und los gehts, egal was der ursprüngliche Autor eigentlich beabsichtigt hat. Offensichtlich hat der Code ja an der Stelle eine Schwachstelle, zumindest aus der Perspektive des individuellen Anwendungsfalls heraus gesehen. Warum sollte ich die nicht für meine Anwendungsfall anpassen (dürfen)? Lizenzrechtliche Geschichten mal außen vor gelassen. Gegen solchen Missbrauch hilft auch kein private.

        Ich würde mir als Autor keine gesteigerten Gedanken über die Verhinderung unerwünschten Ausführens durch legitime Verwender machen (bei unerwünschten Ausnutzern sieht die Sachlage anders aus). Verhindern kann ich es sowieso nicht vollständig.

        dedlfix.

        1. Hey,

          Wovor hast du eigentlich Angst/Bedenken/wasauchimmer? Wenn du Javascript-Code für andere schreibst, läuft der außerhalb deines Einflussbereiches und ist obendrein auch nicht vor Manipulationen und Einblicken geschützt. Im "Notfall" kopiert man sich den Code, korrigiert das ungewünschte Verhalten heraus und los gehts, egal was der ursprüngliche Autor eigentlich beabsichtigt hat.

          Das ist mir bewusst.

          Offensichtlich hat der Code ja an der Stelle eine Schwachstelle, zumindest aus der Perspektive des individuellen Anwendungsfalls heraus gesehen. Warum sollte ich die nicht für meine Anwendungsfall anpassen (dürfen)?

          Dagegen spräche an sich auch nichts.

          Ich sehe aber keinen Sinn darin, Helferfunktionen u.ä. in der Klassenschnittstelle zu verankern. Eine Klasse sollte doch nur jene Eigenschaften/Methoden nach außen hin sichtbar machen, die direkt in Verbindung mit der Klasse selbst stehen, und nicht wahllos jedes Stück Code, auf das sie zurückgreift, denn dann kann ich auch ein ganz normales Objekt benutzen ({}). Ich will schließlich eine Klasse erzeugen, mit der Funktionalität, die ich, als Autor, beabsichtigt habe, und keine Klasse, die zusätzlich dazu auch noch ein schweizer Taschenmesser ist, das zusätzlich zum eigentlichen Verhalten auch noch das und das und das bietet.
          Wenn jemand (widererwartend) eine bestimmte Funktionalität in der Klassenschnittstelle haben will, kann er das gerne tun, wenn er das für sinnvoll hält, das ist seine Sache. Ich, als Autor, halte das aber gerade nicht für sinnvoll und würde das deswegen eigentlich vermeiden.
          Wenn ich an Entwicklungsumgebungen denke, würde ich sagen, dass es auch komisch aussähe, wenn ich eine Methode angeboten bekäme, die nicht direkt dokumentiert ist (da eigentlich private). Ich meine, was sollte ich sagen? „Ignore this.“, „Just don't touch this.“, ...

          Ich würde mir als Autor keine gesteigerten Gedanken über die Verhinderung unerwünschten Ausführens durch legitime Verwender machen (bei unerwünschten Ausnutzern sieht die Sachlage anders aus). Verhindern kann ich es sowieso nicht vollständig.

          Also kann man sagen, dass du auch dafür wärst Symbol() zu benutzen?

          Wobei dann noch die Frage wäre, in wie weit man Symbol() heute schon in produktivem Code benutzen kann? Und generell all die hübschen Sachen, die ES6 zu bieten hat?

          Reinhard

          1. Lieber Reinhard,

            Also kann man sagen, dass du auch dafür wärst Symbol() zu benutzen?

            Wobei dann noch die Frage wäre, in wie weit man Symbol() heute schon in produktivem Code benutzen kann?

            was ist denn am revealing pattern so schlecht, dass man es dafür nicht benutzen sollte? Was hat Symbol() so viel mehr zu bieten, dass es hier erste Wahl sein sollte?

            Und generell all die hübschen Sachen, die ES6 zu bieten hat?

            Das wiederum steht auf einem anderen Blatt.

            Liebe Grüße,

            Felix Riesterer.

            1. Hey,

              was ist denn am revealing pattern so schlecht, dass man es dafür nicht benutzen sollte? Was hat Symbol() so viel mehr zu bieten, dass es hier erste Wahl sein sollte?

              Um das mal klarzustellen: Das Revealing Module Pattern ist in aller Regel mein Mittel der Wahl. In dieser Situation geht es aber darum, eine Klassen-API vernünftig zu gestalten. Hast du einen Blick auf mein (wenn auch sehr bescheidenes) Beispiel geworfen? Ich nutze ja das Revealing Module Pattern, aber ich frage mich, inwiefern die besagte reverse Methode sinnvoll ist. Auf der einen Seite gehört sie insofern zur Klasse, als dass sie eine solche Klasse als Parameter (oder this-Kontext) erwartet, auf der anderen Seite ist es eine Helferfunktion und damit nicht als API-Methode gedacht. Symbol() würde, wie @1unitedpower angemerkt hat, die Helferfunktion als pseudo-private Methode in der API verfügbar machen, aber hätte einen engeren Kontext zur Klasse als wenn auf eine private Variable referenziert werden würde.
              Nichtsdestotrotz, wenn die Verfügbarkeit von Symbol() nicht sicher ist, wären wir doch wieder bei meinem Ausgangsposting.

              Reinhard

              1. Lieber Reinhard,

                Um das mal klarzustellen: Das Revealing Module Pattern ist in aller Regel mein Mittel der Wahl. In dieser Situation geht es aber darum, eine Klassen-API vernünftig zu gestalten.

                hmm, bin mir nicht sicher, ob ich Dein Ansinnen so wirklich verstanden habe.

                Hast du einen Blick auf mein (wenn auch sehr bescheidenes) Beispiel geworfen?

                Ja.

                Ich nutze ja das Revealing Module Pattern,

                Das habe ich so direkt nicht darin erkannt, aber ich bin auch kein JavaScript-Wizard.

                aber ich frage mich, inwiefern die besagte reverse Methode sinnvoll ist.

                Mich stört, dass sie im selben Namensraum herumliegt, wie der Klassenname selbst. Ich hätte sie vom Scope her in die Klasse selbst verfrachtet (siehe mein Beispiel).

                Auf der einen Seite gehört sie insofern zur Klasse, als dass sie eine solche Klasse als Parameter (oder this-Kontext) erwartet, auf der anderen Seite ist es eine Helferfunktion und damit nicht als API-Methode gedacht.

                Warum sollte diese Helferfunktion für andere Klassen erreichbar sein? Benötigen diese auch eine reverse-Methode?

                Symbol() würde, wie @1unitedpower angemerkt hat, die Helferfunktion als pseudo-private Methode in der API verfügbar machen, aber hätte einen engeren Kontext zur Klasse als wenn auf eine private Variable referenziert werden würde.

                Inwiefern dieser engere Kontext tatsächlich nachvollziehbar ist, kann ich nicht beurteilen, da ich nicht verstanden habe, worauf Du wirklich hinaus willst. Das liegt sicherlich an meinen beschränkten Kenntnissen in JavaScript.

                Nichtsdestotrotz, wenn die Verfügbarkeit von Symbol() nicht sicher ist, wären wir doch wieder bei meinem Ausgangsposting.

                Ich würde die Helferfunktion in den Scope von myClass packen. Da gehört es nach meinem Gefühl hin.

                Liebe Grüße,

                Felix Riesterer.

                1. Hey,

                  Ich nutze ja das Revealing Module Pattern,

                  Das habe ich so direkt nicht darin erkannt, aber ich bin auch kein JavaScript-Wizard.

                  IIFE in dem (u.a.) die Klasse myClass gekapselt ist?

                  aber ich frage mich, inwiefern die besagte reverse Methode sinnvoll ist.

                  Mich stört, dass sie im selben Namensraum herumliegt, wie der Klassenname selbst. Ich hätte sie vom Scope her in die Klasse selbst verfrachtet (siehe mein Beispiel).

                  Das stört mich eigentlich nicht so sehr.

                  Ich würde die Helferfunktion in den Scope von myClass packen. Da gehört es nach meinem Gefühl hin.

                  Und genau da ist das Riesen-Problem: Die Helferfunktion soll von den API-Methoden aufgerufen werden, also den Methoden, die ich im Prototyp definiere. Und genau das geht so nicht. Im müsste diese Methoden jedes Mal neu als Eigenschaft einer Instanz definieren. Und das erscheint mir alles andere als toll.

                  Auf der einen Seite gehört sie insofern zur Klasse, als dass sie eine solche Klasse als Parameter (oder this-Kontext) erwartet, auf der anderen Seite ist es eine Helferfunktion und damit nicht als API-Methode gedacht.

                  Warum sollte diese Helferfunktion für andere Klassen erreichbar sein? Benötigen diese auch eine reverse-Methode?

                  Verstehe ich nicht. Die Helferfunktion(en) sind in meinem speziellen Fall nur für eine Klasse gedacht.

                  Symbol() würde, wie @1unitedpower angemerkt hat, die Helferfunktion als pseudo-private Methode in der API verfügbar machen, aber hätte einen engeren Kontext zur Klasse als wenn auf eine private Variable referenziert werden würde.

                  Inwiefern dieser engere Kontext tatsächlich nachvollziehbar ist, kann ich nicht beurteilen, da ich nicht verstanden habe, worauf Du wirklich hinaus willst.

                  Meine Frage zielt darauf ab herauszufinden, wohin man Funktionen schreiben sollte, die mit einer Klasse und ihren Eigenschaften arbeitet, die aber nicht direkt von außerhalb aufgerufen werden sollten (wohl aber von den Methoden im Prototyp), und wie diese aufgebaut sind (siehe reverse1/2 in meinem Ausgangsbeispiel).

                  Reinhard

                  1. Lieber Reinhard,

                    Meine Frage zielt darauf ab herauszufinden, wohin man Funktionen schreiben sollte, die mit einer Klasse und ihren Eigenschaften arbeitet, die aber nicht direkt von außerhalb aufgerufen werden sollten (wohl aber von den Methoden im Prototyp), und wie diese aufgebaut sind (siehe reverse1/2 in meinem Ausgangsbeispiel).

                    na, im Scope der jeweiligen Funktion!

                    (function() {
                        var myClass = function (msg) {
                            var t = this,
                                message = msg;
                    
                            function reverse1 () {
                                return message.split('').reverse().join('');
                            };
                    
                            function reverse2 () {
                                return message.split('').reverse().join('');
                            };
                    
                            t.alert = function () {
                                window.alert(reverse1());
                                window.alert(reverse2());
                            };
                        };
                    
                        var m = new myClass('hello world');
                        m.alert();
                    })();
                    

                    Liebe Grüße,

                    Felix Riesterer.

                    1. Hey,

                      na, im Scope der jeweiligen Funktion!

                      (function() {
                          var myClass = function (msg) {
                              var t = this,
                                  message = msg;
                      
                              function reverse () {
                                  return message.split('').reverse().join('');
                              };
                      
                              t.alert = function () {
                                  window.alert(reverse());
                              };
                          };
                      
                          var m = new myClass('hello world');
                          m.alert();
                      })();
                      

                      Uuh ... Ja, es würde das Helferfunktion-Problem lösen, aber elegant ist das nicht. Für jede Instanz erneut die Helferfunktionen erstellen, erneut die API erstellen, alles in einer eigenen Closure kapseln – da schreit doch die Performance, oder? Deshalb war meine ursprüngliche Intention den Prototyp zu benutzen.

                      Reinhard

                      1. Lieber Reinhard,

                        da schreit doch die Performance, oder?

                        das kommt darauf an, ob Du hier Mikro-Optimierung betreibst, oder ob Du es wirklich mit tausenden von Instanzen zu tun hast, die spürbar langsamer reagieren und den Arbeitsspeicher unnötig belasten.

                        Liebe Grüße,

                        Felix Riesterer.

              2. was ist denn am revealing pattern so schlecht, dass man es dafür nicht benutzen sollte?

                Nichts ist daran schlecht, es ist vielleicht etwas verbose, aber das kriegt man mit Blockscope auch irgendwie gelöst. Man darf sich nur von dem Namen nicht in die Irre führen lassen, es ist ein Muster für Datenkapselung, nicht für Modularisierung. Modulsysteme, wie CommonJS oder ASM, lösen wesentlich komplexere Aufgaben. Sie ermöglichen es dem Entwickler zum Beispiel den Code über mehrere Dateien zu verteilen und die Ladereihenfolge automatisch herauszufinden zu lassen. Man kann damit Module von Drittanbietern (zum Beispiel aus dem npm-Repository oder von GitHub) installieren. Sie helfen sowohl bei der Verwaltung von Versionsabhängigkeiten als auch bei Platform-Unterschieden. Sie bieten sogar Werkzeuge an, um den Code zu optimieren, bspw. hinsichtlich Datenübertragungs-Volumen. Datenkapselung lösen diese Modulsysteme nur beiläufig. Und dennoch machen sie das Revealing-Module-Pattern nicht hinfällig, ich nutze es immer noch in überschaubaren, eingebetteten Szenarien. D.h. wenn ich empfinde, dass ein eigenständiges Modul die Sache unnötig aufblähen würde.

                Ich nutze ja das Revealing Module Pattern, aber ich frage mich, inwiefern die besagte reverse Methode sinnvoll ist. Auf der einen Seite gehört sie insofern zur Klasse, als dass sie eine solche Klasse als Parameter (oder this-Kontext) erwartet, auf der anderen Seite ist es eine Helferfunktion und damit nicht als API-Methode gedacht.

                Unsichtbare Helferfunktionen sind immer gut, um Code-Duplikation zu vermeiden, und gleichzeitig die öffentliche Schnittstelle schmal zu halten. Sie hat also definitiv ihre Existenzberechtigung. Wie genau die Datenkapselung am besten umzusetzen ist, ist dann von Anwendungsfall zu Anwendungsfall zu entscheiden. Da lässt sich keine Regel an einem abstrakten Beispiel ausmachen.

                Symbol() würde, wie @1unitedpower angemerkt hat, die Helferfunktion als pseudo-private Methode in der API verfügbar machen, aber hätte einen engeren Kontext zur Klasse als wenn auf eine private Variable referenziert werden würde.

                Die Symbol-Lösung macht vor allem Sinn, wenn du damit Daten verwalten möchtest, die sich pro Instanz unterscheiden können. Deine Helferfunktion "reverse" fällt nicht in diese Kategorie. Sie arbeitet zwar mit Objekt-Instanzen, aber sie bleibt dennoch immer gleich. Die Lösung habe ich vorgeschlagen, weil sie der Datenkapselung aus klassischen Programmiersprachen sehr nahe kommt und ich vermutete, dass du nach solch einem Muster suchst.

                Eine weitere Alternative, um pro Instanz Daten zu verwalten, wäre übrigens eine Map:

                const privates = new Map();
                export class Person {
                   constructor (age) {
                      privates.set(this,{age});
                   },
                   get age () {
                      return privates.get(this).age;
                   }
                }
                

                Nichtsdestotrotz, wenn die Verfügbarkeit von Symbol() nicht sicher ist, wären wir doch wieder bei meinem Ausgangsposting.

                Der native Browsersupport für EcmaScritp2015 ist heute schon ganz gut. Chrome 98%, FF 90%, Edge 90%. Bis man sie aber guten Gewissens produktiv einsetzen kann, dauert es noch eine ganze Weile, weil man meistens auch ältere Browserversionen unterstützen möchte. Dafür gibt es heute schon Compiler, die EcmaScript2015 Code lesen und in eine niedrigere Version übersetzen. Ich habe dafür früher Babel benutzt und bin jetzt auf TypeScript umgestiegen, weil mir das extrem starke Typsystem einfach zusagt. Mit TypeScript kannst du sogar bis herunter zu EcmaScript 3 kompilieren. Es gibt heute aus meiner Sicht keine guten Gründe mehr dafür noch länger zu warten und sich mit dem vergleichsweise schmalen Sprachkern von EcmaScript 5.1 abzumühen.

                1. Hey,

                  Ich nutze ja das Revealing Module Pattern, aber ich frage mich, inwiefern die besagte reverse Methode sinnvoll ist. Auf der einen Seite gehört sie insofern zur Klasse, als dass sie eine solche Klasse als Parameter (oder this-Kontext) erwartet, auf der anderen Seite ist es eine Helferfunktion und damit nicht als API-Methode gedacht.

                  Unsichtbare Helferfunktionen sind immer gut, um Code-Duplikation zu vermeiden, und gleichzeitig die öffentliche Schnittstelle schmal zu halten. Sie hat also definitiv ihre Existenzberechtigung. Wie genau die Datenkapselung am besten umzusetzen ist, ist dann von Anwendungsfall zu Anwendungsfall zu entscheiden. Da lässt sich keine Regel an einem abstrakten Beispiel ausmachen.

                  Das ist schon ziemlich frustrierend, dass es doch so vieles gibt, auf das es keine klare Aussage gibt, wie „Das ist besser, weil ...“. Besonders, wenn man nicht wirklich auf Erfahrung zurückblicken kann.

                  Symbol() würde, wie @1unitedpower angemerkt hat, die Helferfunktion als pseudo-private Methode in der API verfügbar machen, aber hätte einen engeren Kontext zur Klasse als wenn auf eine private Variable referenziert werden würde.

                  Die Lösung habe ich vorgeschlagen, weil sie der Datenkapselung aus klassischen Programmiersprachen sehr nahe kommt und ich vermutete, dass du nach solch einem Muster suchst.

                  Die Lösung mit Symbol geht in die Richtung zu dem, was ich suche: Innerhalb der Methoden kann ich mit this schalten und walten, da die Methode im Prototyp sitzt, und nach außen hin ist sie so gut wie unsichtbar.
                  Meine beiden Beispiele sind nach außen hin vollkommen unsichtbar, aber ich muss entweder die Klasse als Parameter übergeben, oder die Funktion mit call() im Kontext der Klasse ausführen.
                  Mit Symbol habe ich nun drei Ansätze. Bestimmt hat jeder sein Für und Wider, aber mangels Erfahrung weiß ich nicht, wie ich mich für einen entscheiden soll.

                  Nichtsdestotrotz, wenn die Verfügbarkeit von Symbol() nicht sicher ist, wären wir doch wieder bei meinem Ausgangsposting.

                  Der native Browsersupport für EcmaScritp2015 ist heute schon ganz gut. Chrome 98%, FF 90%, Edge 90%. Bis man sie aber guten Gewissens produktiv einsetzen kann, dauert es noch eine ganze Weile, weil man meistens auch ältere Browserversionen unterstützen möchte. Dafür gibt es heute schon Compiler, die EcmaScript2015 Code lesen und in eine niedrigere Version übersetzen.

                  Wie werden denn bspw. Symbols nach ES5 kompiliert? Das hört sich ziemlich abenteuerlich an, wenn man folgendes bedenkt: typeof Symbol() === 'symbol' // true

                  Mit TypeScript kannst du sogar bis herunter zu EcmaScript 3 kompilieren. Es gibt heute aus meiner Sicht keine guten Gründe mehr dafür noch länger zu warten und sich mit dem vergleichsweise schmalen Sprachkern von EcmaScript 5.1 abzumühen.

                  Ich weiß gar nicht, was es alles in ES3 nicht gibt. Als ich angefangen habe mich mit JS zu beschäftigen, gab es schon eine ganze Weile ES5. Von ES6 wollte ich erst gar nichts wissen, nachdem ich diese Arrow-Funktionen gesehen habe. [1, 2, 3].map(item => item * item) – das sah so schrecklich aus, wer, der davon keine Ahnung hat, sollte daraus schlau werden?
                  Später habe ich mir aber doch alles durchgelesen, was ES6 zu bieten hat, und es ist gar nicht mal soo schlecht. Ganz vorne dabei sind für mich Default-Parameter, Template-Strings und vielleicht Destructuring. Proxies und Reflection dagegen sind ziemlich ... extrem.
                  Heute sieht der Einzeiler mit Arrow-Funktion doch irgendwie ganz schick aus.

                  Reinhard

                  1. Tach!

                    Die Lösung habe ich vorgeschlagen, weil sie der Datenkapselung aus klassischen Programmiersprachen sehr nahe kommt und ich vermutete, dass du nach solch einem Muster suchst.

                    Apropos klassische Programmiersprache, schau dir mal Typescript an. Das ist sozusagen Javascript für C#-Programmierer. Da gibt es private und man muss auch nicht selbst mit den Prototypen rumhampeln. Allerdings transpiliert private nicht zu einem von Plain Old Javascript aus nicht zugreifbaren Mitglied. Die Privatheit wird nur innerhalb der Typescript-Tools sichergestellt.

                    dedlfix.

                    1. Hey,

                      Apropos klassische Programmiersprache, schau dir mal Typescript an. Das ist sozusagen Javascript für C#-Programmierer. Da gibt es private und man muss auch nicht selbst mit den Prototypen rumhampeln. Allerdings transpiliert private nicht zu einem von Plain Old Javascript aus nicht zugreifbaren Mitglied. Die Privatheit wird nur innerhalb der Typescript-Tools sichergestellt.

                      Das heißt, solange ich in TypeScript bin, ist alles private, wo ich „private“ drangeschrieben habe, wenn ich es dann aber nach JavaScript kompiliere, um es bspw. in meiner Website zu benutzen, ist private Geschichte.
                      Dann kann ich doch gleich JavaScript schreiben und mir ganz doll vorstellen, dass das, was private sein soll, private ist. Macht's zwar auf dem Papier nicht besser, aber wenigstens war die Theorie schön gedacht.

                      Reinhard

                      1. Tach!

                        Das heißt, solange ich in TypeScript bin, ist alles private, wo ich „private“ drangeschrieben habe, wenn ich es dann aber nach JavaScript kompiliere, um es bspw. in meiner Website zu benutzen, ist private Geschichte.
                        Dann kann ich doch gleich JavaScript schreiben und mir ganz doll vorstellen, dass das, was private sein soll, private ist. Macht's zwar auf dem Papier nicht besser, aber wenigstens war die Theorie schön gedacht.

                        Kommt drauf an, was man will. Wenn das, was man schreibt, nicht darauf ausgelegt ist, mit POJ angesprochen zu werden, sondern komplett mit Typescript geschrieben werden kann, dann kann man sich genauso auf den Typescript-Transpiler verlassen, wie man sich auf einen Compiler einer herkömmlichen Programmiersprache verlassen kann. Im Speicher gibt es den Unterschied zwischen privat und öffentlich auch nicht mehr. Das ist genauso nur Theorie im Compiler.

                        dedlfix.

                      2. Dann kann ich doch gleich JavaScript schreiben und mir ganz doll vorstellen, dass das, was private sein soll, private ist.

                        Das ist durchaus auch eine legitime Lösung. Es ist eine weit verbreitete Konvention, private Objekteigenschaften mit einem Untertricht zu prefixen. Der Unterstrich fungiert als lockerer Warnhinweis und sagt nur aus "Hierauf ist kein Verlass, hier lauert Gefahr". Was man im Englischen noch viel schöner ausdrücken kann: "Here be dragons".

                        1. Lieber 1unitedpower,

                          sagt nur aus "Hierauf ist kein Verlass, hier lauert Gefahr". Was man im Englischen noch viel schöner ausdrücken kann: "Here be dragons".

                          nein. Die Ausdrucksweise "here be dragons" bedeutet eher: Ab hier unerforschtes (und wahrscheinlich gefährliches) Gebiet.

                          Liebe Grüße,

                          Felix Riesterer.

                          1. Hallo,

                            sagt nur aus "Hierauf ist kein Verlass, hier lauert Gefahr". Was man im Englischen noch viel schöner ausdrücken kann: "Here be dragons".

                            nein. Die Ausdrucksweise "here be dragons" bedeutet eher: Ab hier unerforschtes (und wahrscheinlich gefährliches) Gebiet.

                            wo ist der Unterschied in der Bedeutung? Für mich ist das in etwa dasselbe.

                            So long,
                             Martin

                            --
                            Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
                            - Douglas Adams, The Hitchhiker's Guide To The Galaxy
                  2. Der native Browsersupport für EcmaScritp2015 ist heute schon ganz gut. Chrome 98%, FF 90%, Edge 90%. Bis man sie aber guten Gewissens produktiv einsetzen kann, dauert es noch eine ganze Weile, weil man meistens auch ältere Browserversionen unterstützen möchte. Dafür gibt es heute schon Compiler, die EcmaScript2015 Code lesen und in eine niedrigere Version übersetzen.

                    Wie werden denn bspw. Symbols nach ES5 kompiliert? Das hört sich ziemlich abenteuerlich an, wenn man folgendes bedenkt: typeof Symbol() === 'symbol' // true

                    Der Babel-Compiler macht aus diesem Code zum Beispiel folgendes:

                    'use strict';
                    
                    var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
                    
                    _typeof(Symbol()) === 'symbol';
                    
                2. Hallo 1unitedpower

                  Eine weitere Alternative, um pro Instanz Daten zu verwalten, wäre übrigens eine Map:

                  const privates = new Map();
                  export class Person {
                     constructor (age) {
                        privates.set(this,{age});
                     }, // <--
                     get age () {
                        return privates.get(this).age;
                     }
                  }
                  

                  Abgesehen davon, dass das Komma hier einen Syntaxfehler produzieren würde, denke ich nicht, dass eine Map in so einem Fall das Mittel der Wahl sein sollte, jedenfalls dann nicht, wenn Iteration an dieser Stelle nicht benötigt wird.

                  Das Problem hierbei ist, wie auch bei der Verwendung von Arrays oder von planen Objekten, dass solange die entsprechende Datenstruktur nicht selbst von der Garbage Collection abgeräumt oder das Programm beendet wird, alle von der Klasse erzeugten Instanzen und die dazugehörigen Datenobjekte im Speicher verbleiben, sofern man die Referenzen nicht von Hand wieder entfernt. Das kann abhängig von der Anzahl der von der Klasse erzeugten Instanzen und der Menge der hinterlegten Daten durchaus zum Problem werden.

                  const privates = new Map( );
                  
                  export class Person {
                     constructor (age) {
                        privates.set(this, {age});
                     }
                     deletePrivateData ( ) {
                        privates.delete(this);
                     }
                     get age ( ) {
                        return privates.get(this).age;
                     }
                  }
                  

                  Um diesem Problem zu begegnen, könnte man natürlich wie in dem Beispiel oben eine extra Methode definieren, die aufzurufen wäre, wenn eine Instanz nicht mehr benötigt wird, allerdings würde ich nicht darauf wetten, dass das auch wirklich jemand machen würde, zumal solche Aufräumarbeiten aufgrund der automatischen Speicherbereinigung in ECMAScript erstens unüblich sind, und zweitens die Notwendigkeit dies zu tun von außen ja gerade nicht ersichtlich ist.

                  const privates = new WeakMap( );
                  

                  Wenn Daten mit Objekten zu assoziieren sind ohne sie in den Objekten selbst zu hinterlegen, dann sollte also besser die Verwendung einer WeakMap in Betracht gezogen werden. Wie der Name bereits andeutet, werden hier die Referenzen auf die als Schlüssel verwendeten Objekte schwach gehalten, das heißt, wenn außer dem Eintrag in der WeakMap keine andere, starke Referenz auf ein entsprechendes Objekt mehr besteht, dann wird der Eintrag aus der WeakMap entfernt und das Objekt wird gelöscht, ebenso wie das assoziierte Datenobjekt, auf das nach dem Löschen des Schlüssels auch keine Referenz mehr bestehen sollte.

                  Viele Grüße,

                  Orlok

                  1. Das Problem hierbei ist, wie auch bei der Verwendung von Arrays oder von planen Objekten, dass solange die entsprechende Datenstruktur nicht selbst von der Garbage Collection abgeräumt oder das Programm beendet wird, alle von der Klasse erzeugten Instanzen und die dazugehörigen Datenobjekte im Speicher verbleiben, sofern man die Referenzen nicht von Hand wieder entfernt. Das kann abhängig von der Anzahl der von der Klasse erzeugten Instanzen und der Menge der hinterlegten Daten durchaus zum Problem werden.

                    Wenn Daten mit Objekten zu assoziieren sind ohne sie in den Objekten selbst zu hinterlegen, dann sollte also besser die Verwendung einer WeakMap in Betracht gezogen werden. Wie der Name bereits andeutet, werden hier die Referenzen auf die als Schlüssel verwendeten Objekte schwach gehalten, das heißt, wenn außer dem Eintrag in der WeakMap keine andere, starke Referenz auf ein entsprechendes Objekt mehr besteht, dann wird der Eintrag aus der WeakMap entfernt und das Objekt wird gelöscht, ebenso wie das assoziierte Datenobjekt, auf das nach dem Löschen des Schlüssels auch keine Referenz mehr bestehen sollte.

                    Guter Beitrag, danke. Ich stimme dir zu, leite aber aus daraus nicht ab, dass man WeakMaps allgemeinen Vorzug vor Maps geben sollte. Es ist ein Trade-Off zwischen Speicherbedarf und Performance. Die Garbage-Collection kann selber schnell zu einem Performance-Flaschenhals werden. Viele Performance-Optimierung konzentrieren sich deshalb darauf, die Garbage-Collection möglichst wenig zu beschäftigen. Um eine Entscheidung pro oder kontra WeakMap zu fällen hilft letztendlich nur Memory- und CPU-Profiling. Die Chrome-Dev-Tools haben die richtigen Werkzeuge dafür an Board.

                    1. Um eine Entscheidung pro oder kontra WeakMap zu fällen hilft letztendlich nur Memory- und CPU-Profiling. Die Chrome-Dev-Tools haben die richtigen Werkzeuge dafür an Board.

                      Ich habe da mal einen kleinen Benchmark geschrieben, um das Verhalten der Gabrage-Collection zwischen WeakMap und Map zu untersuchen. Der Benchmark erstellt jeweils 10.000.000 Objekte und speichert sie in der entsprechenden Datenstruktur ab. Ferner wird eine Referenz auf jedes 10te Element im Speicher behalten. Letzteres vor allem, um die Auswirkungen der Garbage-Collection bei der WeakMap zu beobachten. Die Ergebnisse aus Chrome sind teilweise überraschend: Der Map-Benchmark benötigt auf meinem Rechner etwa 15,77 s, WeakMap dagegen 35,78 s. Zusätzlich arbeitet die Garbage-Collection für Map etwa 3,53 s verglichen mit 41,13 s für die WeakMap. Überraschend für mich ist, dass die Garbage-Collection weder bei Map noch bei WeakMap tatsächlich Müll einsammelt, der Speicherbedarf wächst einfach konstant an. Ich hab mal Paul Irish gefragt, was da los ist. Ich vermute, dass mit dem Benchmark etwas nicht stimmt.

          2. Tach!

            Also kann man sagen, dass du auch dafür wärst Symbol() zu benutzen?

            Nicht unbedingt. Alles was nicht an this hängt, ist doch eh private aka local-scope-only. Bisher hatte ich noch keinen Anwendungsfall für "da muss ich jetzt unbedingt ein Symbol verwenden".

            Wobei dann noch die Frage wäre, in wie weit man Symbol() heute schon in produktivem Code benutzen kann? Und generell all die hübschen Sachen, die ES6 zu bieten hat?

            Browserunterstützung recherchieren und entscheiden, ob man mit dieser Version als Minimum leben kann.

            dedlfix.

          3. Hi,

            Ich sehe aber keinen Sinn darin, Helferfunktionen u.ä. in der Klassenschnittstelle zu verankern. Eine Klasse sollte doch nur jene Eigenschaften/Methoden nach außen hin sichtbar machen, die direkt in Verbindung mit der Klasse selbst stehen ...

            Gut, das ist in JavaScript kein Problem. Dazu braucht es keine privaten Member sondern einfach nur einen Scope, die Helferfunktionen einschließt.

            const MyNumber = (() => {
              const next = (a) => a + 1;
              return class {
                constructor() {
                  this.number = 0;
                }
                next() {
                  return next(this.number);
                }
              };
            })();
            

            Wenn ich an Entwicklungsumgebungen denke, würde ich sagen, dass es auch komisch aussähe, wenn ich eine Methode angeboten bekäme, die nicht direkt dokumentiert ist (da eigentlich private).

            Meiner Meinung nach sollten Developer wissen, dass das, was eine Entwicklungsumgebung auf einem Objekt sieht, nicht notwendig die Public API ist... Außerdem versteht eine gute Entwicklungsumgebung entweder JSDoc-Annotationen oder separate Typdefinitionen (übrigens unabhängig davon, ob der Code in TypeScript geschrieben ist). Damit passieren keine Verwechslungen.

            Nick

            1. Lieber Nick,

              const MyNumber = (() => {
                const next = (a) => a + 1;
                return class {
                  constructor() {
                    this.number = 0;
                  }
                  next() {
                    return next(this.number);
                  }
                };
              })();
              

              verstehe ich Deinen Code richtig? In "bisherigem JavaScript" würde ich den nämlich so notieren:

              const MyNumber = (function () {
                function next (a) { return a +1; }
                return {
                  constructor: function () { this.number = 0; }
                  next: function () { return next(this.number); }
                };
              }());
              

              Liebe Grüße,

              Felix Riesterer.

              1. Hallo Felix

                const MyNumber = (() => {
                  const next = (a) => a + 1;
                  return class {
                    constructor() {
                      this.number = 0;
                    }
                    next() {
                      return next(this.number);
                    }
                  };
                })();
                

                verstehe ich Deinen Code richtig?

                Nein. Die Methode next innerhalb des Körpers der Klasse wird automatisch auf dem prototypischen Objekt des Konstruktors definiert, der durch die pseudomethode constructor repräsentiert wird. Das mit den Klassen ist ja im Wesentlichen nur syntaktischer Zucker.

                Das Beispiel von Nick wäre also eher so zu übersetzen:

                const MyNumber = (function ( ) {
                  const next = function (a) {
                    return a + 1;
                  }
                  const constructor = function ( ) {
                    this.number = 0;
                  }
                  constructor.prototype.next = function ( ) {
                    return next(this.number);
                  }
                  return constructor;
                }( ));
                

                Wobei die innerhalb der Klasse definierte Methode standardmäßig nicht abzählbar ist, im Gegensatz zu der durch einen Zuweisungsausdruck erstellten Methode in diesem Beispiel. Wenn man es hätte genau nehmen wollen, hätte man die Methode also explizit definieren müssen. Aber das nur nebenbei bemerkt.

                Viele Grüße,

                Orlok

  2. Lieber Reinhard,

    private Eigenschaften und/oder Methoden kenne ich in JavaScript nur über das revealing pattern:

    function MyConstructor (param) {
      var priv = param;
    
      this.setPriv = function (val) {
          priv = val;
      };
    
      this.getPriv = function () {
        return priv;
      };
    
      function privateAlert (txt) {
        alert(txt);
      }
    
      this.checkError = function () {
        if (!priv) {
          privateAlert("priv is empty!");
        }
      };
    }
    
    const myObj = new MyConstructor(false);
    myObj.checkError(); // "priv is empty!"
    alert(myObj.priv); // undefined (nicht false!)
    myObj.privateAlert("Hallo Welt!"); // TypeError: myObj.privateAlert is not a function
    

    Sowohl die Variable priv, als auch die Funktion/Methode privateAlert können nicht über myObj.priv bzw. myObj.privateAlert adressiert werden. Sie sind durch Ausnutzung des Scope quasi private Eigenschaft bzw. Methode.

    Liebe Grüße,

    Felix Riesterer.

  3. (function() {
        var myClass = function(msg) {
            this.msg = msg;
        };
        myClass.prototype.alert = function() {
            window.alert(reverse1(this));
            window.alert(reverse2.call(this));
        };
        
        var reverse1 = function(myC) {
            return myC.msg.split('').reverse().join('');
        };
        var reverse2 = function() {
            return this.msg.split('').reverse().join('');
        };
        
        var m = new myClass('hello world');
        m.alert();
    })();
    

    Sehe ich das richtig, dass eine „Privatisierung“ der Methode reverse nur durch die oben gezeigten Varianten zu erreichen ist?

    Ja. "Privatheit" gibt es in JavaScript nur in Form von Function Scope (Closures) und Block Scope.

    Wenn Ja, welche ist zu bevorzugen?

    Anhand dieses gestellten Beispiels kann man das m.E. nicht sinnvoll beantworten. Daher meine generelle Antwort:

    Du hast eine (im funktionalen Sinne) "reine" Funktion. Sie nutzt einen Wert nutzt, den sie als Parameter bekommt. Und sie gibt einen neuen Wert zurück.

    Funktionen sind in JavaScript die einfachste und flexibelste Technik, um Code zu strukturieren und wiederzuverwenden. Funktionen sind das, was JavaScript schön macht.

    Mein Tipp: Lass eine Funktion eine Funktion sein. Sie muss keine Methode sein. Sie muss nicht privat (in einem Scope gekapselt) sein. Idealerweise ziehst du sie aus der Klasse heraus und machst sie eigenständig und wiederverwendbar.

    Warum muss es eine Methode sein? Warum eine private Methode? Welche Vorteile hat das?

    Nick