Felix Riesterer: Verständnisfrage JavaScript getter und setter

Liebe JavaScript-Experten, lieber @Rolf b

ich habe im Wiki ein bisschen gestöbert (schaue oft nach aktuellen Änderungen) und Rolfs Benutzerseite zu JavaScript-Objekten gefunden. Da habe ich dann zu getter und setter gelesen und prompt ein paar Fragen.

Wenn ich eine Methode mit get und set definiere, dann definiere ich im Grunde ja zwei Methoden mit demselben Bezeichner. Werden diese beiden darüber unterschieden, dass ich entweder ein oder mehrere Argumente übermittle, oder eben keines? Und wenn dem so ist, wäre es dann nicht sinnvoll mit diesen Methoden einen Wert zu verändern, der ansonsten unerreichbar wäre (eine Variable in einer Closure, deren getter und setter dann natürlich nach dem revealing pattern erstellt werden müssten), anstatt eine Eigenschaft mit einem Bezeichner mit einer beliebigen Anzahl an Unterstrichen zu versehen?

Zwei Beispiele, die meine Frage veranschaulichen sollen:

const myCar = {
    _color: "red", // von außen erreichbare Eigenschaft
    get color: function () { return this._color; },
    set color: function (value) { this._color = value; },
    _type: "SUV",  // von außen erreichbare Eigenschaft
    get type: function () { return this._type; },
    set type: function (value) { this._type = value; }
};

function AnyonesCar () {
    var color, type;  // von außen unerreichbare Variablen

    if (arguments.length > 0) {
        type = arguments[0];
    }

    if (arguments.length > 1) {
        color = arguments[1];
    }

    this.color = function () { // von außen erreichbare Methode
        if (arguments.length > 0) {
            color = arguments[0]; // tut wie setter
        };

        return color; // getter: in Closure eingeschlossene Variable
    };

    this.type = function () { // von außen erreichbare Methode
        if (arguments.length > 0) {
            type = arguments[0]; // tut wie setter
        };

        return type; // getter: in Closure eingeschlossene Variable
    };
}

const yourCar = AnyonesCar("station wagon", "green");

Mir ist nicht bekannt, inwiefern man aus einem Objektliteral einen Konstruktor machen könnte - aber das hat sicher mit Cloning zu tun (vielleicht mit const myOtherCar = new Object(myCar)?).

Worum es mir geht, ist die Frage, ob ein revealing pattern in JavaScript(!) nicht sinnvoller ist, als das in anderen Sprachen eher übliche Arbeiten mit getter und setter.

Liebe Grüße,

Felix Riesterer.

  1. Tach!

    Wenn ich eine Methode mit get und set definiere, dann definiere ich im Grunde ja zwei Methoden mit demselben Bezeichner. Werden diese beiden darüber unterschieden, dass ich entweder ein oder mehrere Argumente übermittle, oder eben keines?

    Das ist eine Frage nach den Interna der Engine. Als Verwender sieht es für dich ja nur wwie ein Variablenzugriff aus. Und das ist entweder ein lesender oder ein schreibender Zugriff. Darüber kann unterschieden werden, ob der Getter oder der Setter aufgerufen werden muss.

    Und wenn dem so ist, wäre es dann nicht sinnvoll mit diesen Methoden einen Wert zu verändern, der ansonsten unerreichbar wäre (eine Variable in einer Closure, deren getter und setter dann natürlich nach dem revealing pattern erstellt werden müssten), anstatt eine Eigenschaft mit einem Bezeichner mit einer beliebigen Anzahl an Unterstrichen zu versehen?

    Unterstriche sind Kosmetik und Coding Style. Es kommt ja drauf an, was du mit dem Getter/Setter bezweckst. Wenn du nur einen simplen Variablenzugriff haben möchtest, sind Getter/Setter überdimensioniert. Sie haben erst dann einen bedeutenden Vorteil, wenn du neben dem oder statt des Durchreichen(s) eines privat abgelegten Wertes noch irgendwas anderes tust, wie einen Wert zu berechnen. Zum Beispiel kann die Eigenschaft Fullname einen Getter ohne Setter haben und beim Zugriff die ansonsten einzeln gespeicherten Werte zu Vor- und Nachnamen und ein Leerzeichen zusammengebracht liefern. Ein schreibender Zugriff ist dafür weniger sinnvoll, weswegen der wegen fehlendem Setter abgelehnt wird.

    Zwei Beispiele, die meine Frage veranschaulichen sollen:

    const myCar = {
        _color: "red", // von außen erreichbare Eigenschaft
        get color: function () { return this._color; },
        set color: function (value) { this._color = value; },
        _type: "SUV",  // von außen erreichbare Eigenschaft
        get type: function () { return this._type; },
        set type: function (value) { this._type = value; }
    };
    

    Das sind lediglich 1:1-Durchreicher ohne gesteigerten Nutzen für den Einsatz von Gettern/Settern.

        this.color = function () { // von außen erreichbare Methode
            if (arguments.length > 0) {
                color = arguments[0]; // tut wie setter
            };
    
            return color; // getter: in Closure eingeschlossene Variable
        };
    

    Ja, auf diese Weise mit nur einer Funktion und dem Prüfen ob ein Parameter übergeben wurde oder nicht, habe ich das früher auch mal (aber nicht im Javascript-Umfeld) gemacht, um Getter/Setter nachzubilden. Der Nachteil ist aber, dass du für die Prüfung auf Argumente Code einbauen musst, der nicht auf den ersten Blick selbsterklärend ist.

    Es geht aber auch die Verwendung von arguments:

    function foo(bar) {
      if (bar != undefined) {
        // ...
      }
    }
    

    Worum es mir geht, ist die Frage, ob ein revealing pattern in JavaScript(!) nicht sinnvoller ist, als das in anderen Sprachen eher übliche Arbeiten mit getter und setter.

    Für welchen Anwendungsfall?

    dedlfix.

    1. @@dedlfix

      Zum Beispiel kann die Eigenschaft Fullname einen Getter ohne Setter haben und beim Zugriff die ansonsten einzeln gespeicherten Werte zu Vor- und Nachnamen und ein Leerzeichen zusammengebracht liefern

      Gutes Beispiel.

      Obwohl … nicht ganz so gut. Das macht aus dem Familiennamen Mao und dem Vornamen[1] Zedong den vollen Namen „Zedong Mao“ – falsche Reihenfolge.

      LLAP 🖖

      --
      “You might believe there are benefits for the developer, but first of all, you should put those behind the interest of the user.” —Stefan Tilkov
      Selfcode: sh:) fo:} ch:? rl:) br:> n4:& va:| de:> zu:} fl:{ ss:| ls:# js:|

      1. siehe Anmerkung des Übersetzers ↩︎

      1. Moin,

        Zum Beispiel kann die Eigenschaft Fullname einen Getter ohne Setter haben und beim Zugriff die ansonsten einzeln gespeicherten Werte zu Vor- und Nachnamen und ein Leerzeichen zusammengebracht liefern

        Gutes Beispiel.

        Obwohl … nicht ganz so gut. Das macht aus dem Familiennamen Mao und dem Vornamen Zedong den vollen Namen „Zedong Mao“ – falsche Reihenfolge.

        nein, falsche Annahme. ;-)
        Natürlich würde so eine getter-Funktion dem eingestellten locale entsprechend verfahren.

        So long,
         Martin

        --
        Logik ist die Theorie, Chaos die Praxis.
        1. Hallo,

          nein, falsche Annahme. ;-)
          Natürlich würde so eine getter-Funktion dem eingestellten locale entsprechend verfahren.

          Möchtest du damit andeuten, dass ich in China "Kalk Tabellen" hieße?

          Gruß
          Kalk

          1. Hallo

            nein, falsche Annahme. ;-)
            Natürlich würde so eine getter-Funktion dem eingestellten locale entsprechend verfahren.

            Möchtest du damit andeuten, dass ich in China "Kalk Tabellen" hieße?

            Wenn du hier „Tabellen Kalk“ hießest …

            … So heißt du aber nicht. Du heißest „Tabellenkalk“, was, in Ermangelung eines trennenden Leerzeichens, zum bekanntermaßen originär chinesischen „Tabellenkalk“ würde.

            ;-)

            Tschö, Auge

            --
            Wir hören immer wieder, dass Regierungscomputer gehackt wurden. Ich denke, man sollte die Sicherheit seiner Daten nicht Regierungen anvertrauen.
            Jan Koum, Mitgründer von WhatsApp, im Heise.de-Interview
            1. Hi,

              Natürlich würde so eine getter-Funktion dem eingestellten locale entsprechend verfahren.

              Möchtest du damit andeuten, dass ich in China "Kalk Tabellen" hieße?

              Wenn du hier „Tabellen Kalk“ hießest …

              … So heißt du aber nicht. Du heißest „Tabellenkalk“, was, in Ermangelung eines trennenden Leerzeichens, zum bekanntermaßen originär chinesischen „Tabellenkalk“ würde.

              außerdem gelten für Amts- und Berufsbezeichnungen natürlich andere Regeln als für Namen. :-)

              So long,
               Martin

              --
              Logik ist die Theorie, Chaos die Praxis.
              1. Hallo,

                … So heißt du aber nicht. Du heißest „Tabellenkalk“, was, in Ermangelung eines trennenden Leerzeichens, zum bekanntermaßen originär chinesischen „Tabellenkalk“ würde.

                außerdem gelten für Amts- und Berufsbezeichnungen natürlich andere Regeln als für Namen. :-)

                Menno, da will man mit einem Augenzwinkern auf Argumentationsprobleme aufmerksam machen und scheitert an Formalismen.

                Ich glaub, ich sollte mir mal einen neuen Namen auswürfeln.

                Gruß
                Kalk

      2. Tach!

        Zum Beispiel kann die Eigenschaft Fullname einen Getter ohne Setter haben und beim Zugriff die ansonsten einzeln gespeicherten Werte zu Vor- und Nachnamen und ein Leerzeichen zusammengebracht liefern

        Gutes Beispiel.

        Obwohl … nicht ganz so gut. Das macht aus dem Familiennamen Mao und dem Vornamen[^1] Zedong den vollen Namen „Zedong Mao“ – falsche Reihenfolge.

        Guter Einwand.

        Obwohl … nicht ganz so gut. Ich habe nicht geschrieben, in welcher Reihenfolge man die zwei Werte und das Stringliteral zusammenbringen soll. Das Leerzeichen ist sogar erst hinter den beiden Namensteilen erwähnt. Zudem bezieht sich dein Einwand auf einen konkreten Anwendungsfall und mein Beispiel ist abstrakt. Und auch wenn man die von dir hineininterpretierte Reihenfolge annimmt, ist es für noch viel mehr Anwendungsfälle nicht direkt einsetzbar. Aber darum ging es ja gar nicht, sondern nur um ein Beispiel für einen Getter aufzuzeigen.

        dedlfix.

  2. Hi,

    Wenn ich eine Methode mit get und set definiere, dann definiere ich im Grunde ja zwei Methoden mit demselben Bezeichner.

    Du definierst keine zwei Methoden, sondern eine Eigenschaft, hinter der zwei Funktionen stehen.

    Werden diese beiden darüber unterschieden, dass ich entweder ein oder mehrere Argumente übermittle, oder eben keines?

    Du rufst die Getter und Setter nicht wie Methoden auf. Du liest bzw. schreibst einfach das Attribut.

    alert(myCar.color); // Getter
    myCar.color = 'red' // Setter
    alert(myCar.type);  // Getter
    myCar.type = 'SUV'  // Setter
    

    Vielleicht verwechselst du das mit anderen Sprachen, wo Getter und Setter normale Methoden sind (Java?):

    object.getProperty();
    object.setProperty(value);
    

    Das kann man in JS zwar auch machen, das ist aber nicht gemeint, wenn von Gettern und Settern die Rede ist. Da geht es um das Lesen und Setzen von Eigenschaften. Man kann zwei Funktionen hinterlegen, die aufgerufen werden, wenn objekt.eigenschaft gelesen bzw. mit objekt.eigenschaft = ... geschrieben wird.

    Und wenn dem so ist, wäre es dann nicht sinnvoll mit diesen Methoden einen Wert zu verändern, der ansonsten unerreichbar wäre (eine Variable in einer Closure, deren getter und setter dann natürlich nach dem revealing pattern erstellt werden müssten), anstatt eine Eigenschaft mit einem Bezeichner mit einer beliebigen Anzahl an Unterstrichen zu versehen?

    Ja, das wäre besser. Ansonsten haben die Getter/Setter keinen Vorteil gegenüber einer "dummen" Eigenschaft. Zwingend nötig ist es aber nicht.

    Die Lösung mit einer Closures und einer "überladenen" Methode mit zwei Aufrufweisen ist eine Möglichkeit. Dabei ist es einfach, den eigentlichen Wert zu verstecken. Dafür hat man eine zusätzliche Schreibweise anstatt einfach objekt.eigenschaft zum Lesen und objekt.eigenschaft = ... zum Schreiben.

    Erst mit Symbols in ECMAScript 6 hat man die Möglichkeit, nach außen hin effektiv "unsichtbare" Eigenschaften hinzuzufügen. Dabei muss man aber auch eine Closure oder einen Block verwenden, um das Symbol zu verstecken. ;)

    let makeCar;
    {
      const typeSymbol = Symbol();
      makeCar = () => {
        return {
          get type() {
            return this[typeSymbol];
          },
          set type(value) {
            this[typeSymbol] = value;
          }
        };
      };
    }
    const myCar = makeCar();
    myCar.type = 'SUV';
    console.log(myCar.type);
    

    Sinnvoll ist das im Vergleich zu einfachen Objekten nur, wenn im Getter oder Setter noch weiterer Code steht, z.B. eine Validierung im Setter vorgenommen wird.

    Rey

  3. Meine Güte, da arbeitet man mal ein paar Stunden für die Brötchen und verpasst gleich eine heiße Diskussion :)

    Ich werde in meinem Artikel noch genauer beschreiben, wo sich die Funktionen für Getter und Setter verstecken; aber im Wiki gibt's dazu durchaus schon knappe Infos, z.B. unter defineProperty und getOwnPropertyDescriptor - die ich dann auch ausbauen werde sofern mir keiner zuvorkommt :)

    Die Methode mit kombinierten get/set Methoden - also farbe() liest und farbe('rot') schreibt - ist ein Notbehelf, den man in etlichen Bibliotheken findet, die eine möglichst breite Plattform abdecken wollen. Spontan fallen mir da jQuery und knockoutJs ein. Richtige syntaxgezuckerte Properties mit get und set - wie man sie beispielsweise aus C# kennt, sind das aber nicht. Die erstellt man in JS mit der get name() { bla } und set name(wert) {blah} Syntax, oder mit Object.defineProperty.

    Dass der Typ Symbol in ES6 neu ist, wusste ich, habe mir den Sinn davon aber noch nicht angelesen. Jetzt weiss ich immerhin was man damit anstellen kann.

    Der Nachteil von get/set Properties ist - wenn man KEINE Symbols hat - dass man sie zwingend auf der Objektinstanz definieren muss wenn sie private Daten verwenden sollen. Weil - man muss ja eine Closure erzeugen, die die Daten versteckt, und das muss pro Instanz geschehen. Muss man viele Objekte erzeugen, ist das eine böse Bremse. Wenn man get/set Properties auf dem Prototypen definiert, dann musste man bisher mit "private by convention" arbeiten, also Unterstrich-Properties. Die neue Nummer mit Symbols ist da richtig genial, das schau ich mir gerne noch an und arbeite es in meinen Artikel ein.

    Gruß Rolf