ROGA: JavaScript

Hallo,

ich suche Hilfe im Bereich JavaScript Programmierung. Ich bin (fast) Neuling und möchte gerne mehr über JavaScript lernen. Im Internet bin ich auf meiner Suche nach Hilfe auf dieses Forum hier gestossen. Meine Frage dazu:

  • bin ich für mein Anliegen hier im richtigen Forum oder kann mir jemand ein Forum dafür empfehlen?
  1. Hallo ROPA,

    herzlich willkommen bei SELFHTML,

    ich suche Hilfe im Bereich JavaScript Programmierung. Ich bin (fast) Neuling und möchte gerne mehr über JavaScript lernen.

    Sehr schön. Wie viele Vorkenntnisse über Programmierung, Erstellung von Webseiten und Computer im Allgemeinen hast du denn?

    • bin ich für mein Anliegen hier im richtigen Forum [...] ?

    Ja, natürlich, wobei ein Forum eher konkreten Fragen dient, wenn man nicht weiter weiß. Besser wären Tutorials, die man alleine (oder mit einem Kumpel) durcharbeitet.

    Für den Anfang:

    Herzliche Grüße

    Matthias Scharwies

    --
    Das wirksamste Mittel gegen Sonnenbrand
    ist Urlaub am Ostseestrand!
    1. Hallo Matthias,

      Danke für die nette Begrüssung 😉

      Wie viele Vorkenntnisse über Programmierung, Erstellung von Webseiten und Computer im Allgemeinen hast du denn?

      Erfahrungen habe ich hauptsächlich mit VBS Scripten, Batch und Bash. Ab und an habe ich auch VBA oder VB programmiert, aber nie professionell eher lösungsorientiert. Auch mit PHP hatte ich schon das eine oder andere kleine Projekt verwirklicht.

      Bezüglich Webseiten habe ich Kenntnisse mit Apache, MySQL und Joomla auf Linux-Basis. Auch habe ich schon meine eigene Website mit HTML Gerüst etc. selbst entwickelt und mit CSS die optische Gestaltung realisiert. Das ganze halt eher im privaten Bereich als Hobby.

      Mit Computern kenne ich mich grundsätzlich gut aus, arbeite beruflich als ICT System Engineer und fühle mich halt eher dort sehr heimisch 😀

      Warum ich gerade an JavaScript interesse habe, liegt daran, dass ich Zuhause angefangen habe, mich mit SmartHome zu beschäftigen. Dabei bin ich unter anderem auf den IOBroker gestossen, der mir auf anhieb sehr gut gefallen hat. Um effiziente Steuerungen zu verwirklichen, bietet der IOBroker neben anderen Scripten eben auch das JavaScript an, was für mich jetzt am meisten Sinn macht. Dass sind also meine Beweggründe, weshalb ich JavaScript erlernen möchte.

      Für den Anfang:

      Danke für den Link, da bin ich mich schon bereits am durchackern und habe deshalb auch schon konkrete Fragen.

      Ja, natürlich, wobei ein Forum eher konkreten Fragen dient, wenn man nicht weiter weiß. Besser wären Tutorials, die man alleine (oder mit einem Kumpel) durcharbeitet.

      Das mit den Tutorials ist ja eine feine Sache, aber manchmal versteht man halt eben nicht alles auf anhieb (und auch nicht nach mehrmaligem durchlesen! 😟) oder möchte etwas erreichen, was so (noch) nicht erklärt wird, weshalb es da dann eben wünschenswert wäre, wenn man Erfahrene Profis um Rat und Tat anfragen könnte.

      In meinem konkreten Fall lerne ich gerade aus dem obigen Tutorial, wie in JavaScript Objekte erstellt und genutzt werden können. Scheint eine tolle Sache zu sein und vermutlich genau dass, was ich in meinem IOBroker-Projekt möglicherweise gut einsetzen könnte.

      Mein Gedanke: Ein Objekt in JavaScript erstellen, dass z.B. meine PV-Anlage abbildet. So hätte ich dann quasi ein Objekt, dass ich nach allen erdenklichen Daten ganz einfach und bequem abfragen könnte, so mein Gedanke!

      const PVA = new Object();
      

      So ungefähr wäre es zumindest gemäss dem Tutorial. Nun wollte ich nicht gleich hier das Objekt mit Eigenschaften abfüllen, sondern wollte quasi eine Unterkategorie (Batterie) erstellen, die dann entsprechende Eigenschaften hätte. z.B. so:

      PVA.Batterie.Status = getSate('sun2000.0.collected.SOC').val;
      PVA.Batterie.LadungEntladung = getSate('sun2000.0.collected.chargeDischargePower').val;
      

      etc. etc.

      doch das funktioniert so scheinbar nicht. Wie erstelle ich also konkret ein Objekt, dass danach beliebig viele Unterkategorien hat, in denen sich dann die eigentlichen Eigenschaften befinden, die man gerne abfragen möchte.

      Ich hoffe, man versteht meinen Gedanken dahinter. Falls ja, hoffe ich, jemand kann mich hier in die richtige Richtung lenken um das zu verwirklichen.

      Vielen Dank schon mal im Voraus.

      1. Hallo ROGA,

        Erfahrungen habe ich hauptsächlich mit VBS Scripten, Batch und Bash. Ab und an habe ich auch VBA oder VB [...] Auch mit PHP hatte ich schon das eine oder andere kleine Projekt verwirklicht.

        Das klingt doch ganz gut! Dann hast du ein grundlegendes Verständnis, wie man programmiert und es geht eher um die Eigenheiten von JavaScript.

        Warum ich gerade an JavaScript interesse habe, liegt daran, dass ich Zuhause angefangen habe, mich mit SmartHome zu beschäftigen.

        Ein sehr interessantes Thema!


        Mein Gedanke: Ein Objekt in JavaScript erstellen, dass z.B. meine PV-Anlage abbildet. So hätte ich dann quasi ein Objekt, dass ich nach allen erdenklichen Daten ganz einfach und bequem abfragen könnte, so mein Gedanke!

        const PVA = new Object();
        

        So ungefähr wäre es zumindest gemäss dem Tutorial.

        Ja, funzt:

        	const PVA = new Object();
        	console.log(PVA);
            
        

        Und dann mit der Konsole mit [F12] untersuchen.

        Nun wollte ich nicht gleich hier das Objekt mit Eigenschaften abfüllen, sondern wollte quasi eine Unterkategorie (Batterie) erstellen,

        Was ist eine Kategorie? Objekte haben Eigenschaften, Methoden und (Unter)-Objekte
        (siehe: JavaScript/Tutorials/OOP/Objekte_und_ihre_Eigenschaften)

        die dann entsprechende Eigenschaften hätte. z.B. so:

        PVA.Batterie.Status = getSate('sun2000.0.collected.SOC').val;
        PVA.Batterie.LadungEntladung = getSate('sun2000.0.collected.chargeDischargePower').val;
        

        Heißt das evtl. getState()?

        doch das funktioniert so scheinbar nicht. Wie erstelle ich also konkret ein Objekt, dass danach beliebig viele Unterkategorien hat, in denen sich dann die eigentlichen Eigenschaften befinden, die man gerne abfragen möchte.

        Siehe das oben verlinkte Tutorial!

        Herzliche Grüße

        Matthias Scharwies

        --
        Das wirksamste Mittel gegen Sonnenbrand
        ist Urlaub am Ostseestrand!
      2. Hallo ROGA,

        an Code wie a = obj1.Dings.Bums.value sind automatisch mehrere Objekte beteiligt. In obj1 findest Du ein Objekt mit Eigenschaft Dings. In der Eigenschaft Dings findest Du ein weiteres Objekt, das die Eigenschaft Bums hat. Und in Bums steht ein Objekt mit der Eigenschaft value.

        Um sowas zu erzeugen, kannst Du geschachtelte Objektliterale verwenden. Das könnte so aussehen (ich habe noch ein paar weitere Eigenschaften hinzugemixt):

        const obj1 = {
           Dings: {
              Bums: {
                 value: 8,
                 name: 'Hugo'
              },
              Blub: 7
           },
           Bla: 3
        };
        

        Wenn Du sowas per Code Stück für Stück aufbaust, musst Du die Objekte Stück für Stück zusammensetzen. An Stelle von new Object() (was nicht falsch ist), kann man einfacher das leere Objektliteral {} verwenden, damit passiert das Gleiche.

        const obj1 = {};
        obj1.Dings = {};
        obj1.Bla = 3;
        obj1.Dings.Bums = {};
        obj1.Dings.Blub = 7;
        obj1.Dings.Bums.value = 8;
        obj1.Dings.Bums.name = 'hugo';
        

        Das Objekt, das so entsteht, ist das Gleiche - es ergibt nur keinen Sinn, es so zu tun, wenn die Zuweisungen wie hier direkt untereinander stehen. Aber wenn Du die Teile über mehrere Codestellen verteilt einsammelst, ergibt das mehr Sinn.

        Generell - bevor Du zu viel baust, beschäftige Dich zunächst mal damit, was der Adapter für deine Solaranlage von sich aus anbietet. Es kann gut sein, dass der bereits eine Objekthierarchie bereitstellt.

        Ich habe gerade nur etwas die Doku von IOBroker überflogen, und bin erstmal von der Komplexität erschlagen. Das ganze Ding basiert auf Node.js - was wichtig ist zu wissen. Das ist auch eine JavaScript-Umgebung, aber es ist kein Browser. D.h. es gibt dort ganz andere Programmierschnittstellen. Der JavaScript-Kern ist gleich, aber es gibt eine Menge drumherum, und das Drumherum ist im Browser und in Node sehr verschieden. Unser Wiki bezieht sich nur auf JavaScript im Browser. Der /JavaScript-Teil geht vor allem auf JavaScript ein, aber es kann auch sein, dass einige Beispiele Dinge tun, die nur im Browser funktionieren.

        Möglicherweise - soweit habe ich die Doku nicht studiert - kann man im IOBroker auch Script bereitstellen, das auf den von IOBroker ausgelieferten HTML Seiten abläuft. Du musst also immer genau aufpassen, für welche Plattform Du programmierst.

        Diese "getSate"-Funktion (die vermutlich getState heißt) - ist das etwas von Dir oder von IOBroker?

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          an Code wie a = obj1.Dings.Bums.value sind automatisch mehrere Objekte beteiligt. In obj1 findest Du ein Objekt mit Eigenschaft Dings. In der Eigenschaft Dings findest Du ein weiteres Objekt, das die Eigenschaft Bums hat. Und in Bums steht ein Objekt mit der Eigenschaft value.

          Vielen Dank für deine Erläuterungen. Genau dieses Verständnis hat mir gefehlt und habe ich im Tutorial so leider auf die schnelle auch nicht finden können. Aber so wie von dir erklärt, habe ich es jetzt (so bilde ich es mir zumindest jetzt ein) verstanden.

          Um sowas zu erzeugen, kannst Du geschachtelte Objektliterale verwenden. Das könnte so aussehen (ich habe noch ein paar weitere Eigenschaften hinzugemixt):

          const obj1 = {
             Dings: {
                Bums: {
                   value: 8,
                   name: 'Hugo'
                },
                Blub: 7
             },
             Bla: 3
          };
          

          Objektliterale konnte ich im Tutorial bereits nachlesen und habe ich in der Theorie soweit auch verstanden. Nur der Zusammenbau des von mir gewünschten Objekts mittels Verschachtelungen von mehreren Objekten war mir absolut nicht klar. Dein Beispiel finde ich aber super, da es genau den Strukturaufbau zeigt, den ich mir für mein Vorhaben vorgestellt habe.

          Wenn Du sowas per Code Stück für Stück aufbaust, musst Du die Objekte Stück für Stück zusammensetzen. An Stelle von new Object() (was nicht falsch ist), kann man einfacher das leere Objektliteral {} verwenden, damit passiert das Gleiche.

          const obj1 = {};
          obj1.Dings = {};
          obj1.Bla = 3;
          obj1.Dings.Bums = {};
          obj1.Dings.Blub = 7;
          obj1.Dings.Bums.value = 8;
          obj1.Dings.Bums.name = 'hugo';
          

          Das Objekt, das so entsteht, ist das Gleiche

          Verstehe ich das nun richtig:

          In deinem ersten Beispiel wird ein neues Objekt komplett mittels einer objektliteralen Struktur zusammengebaut und als Konstante mit der Bezeichnung obj1 bereitgestellt.

          In deinem zweiten Beispiel geschieht im Prinzip dasselbe, jedoch wird das Objekt nicht mit einer Struktur als ganzes gebaut sondern laufend mit direkten Zuweisungen Name = Wert aufgebaut/erweitert.

          • es ergibt nur keinen Sinn, es so zu tun, wenn die Zuweisungen wie hier direkt untereinander stehen. Aber wenn Du die Teile über mehrere Codestellen verteilt einsammelst, ergibt das mehr Sinn.

          Das habe ich jetzt nicht ganz verstanden.... wieso ergibt es so keinen Sinn?

          Generell - bevor Du zu viel baust, beschäftige Dich zunächst mal damit, was der Adapter für deine Solaranlage von sich aus anbietet. Es kann gut sein, dass der bereits eine Objekthierarchie bereitstellt.

          Der Adapter für den Wechselrichter im IOBroker bringt sehr viele Datenpunkte, aber nicht alle sind für mich von Bedeutung. Der Adapter selbst hat auch eine Art "Struktur" oder "Objekthierachie", jedoch nicht als JavaScript Objekt, sondern eben nur als Adapter mit Datenpunkten. Die relevanten Datenpunkte müssen dann in einem JavaScript mittels der von IOBroker zur Verfügung gestellter Funktion getState() in nutzbare Variabeln eingelesen werden. So habe ich das aktuell bei mir umgesetzt, was mir persönlich aber nicht so gut gefällt. Ich würde gerne ein JavaScript schreiben, dass mir alle relevanten Datenpunkte des Adapters ausliest und diese dann in einem einzigen JavaScript-Objekt in realtime (das wird wohl noch eine andere Herausforderung werden???) vereint. Dieses JavaScript könnte ich dann in allen anderen JavaScripten wiederverwenden. Sollte ich einmal Anpassungen am JavaScript-Objekt vornehmen müssen, so muss ich dass dann nur in dem einen Script anpassen und für alle anderen Scripte (die dieses Script includen) wäre das dann automatisch ebenfalls angepasst. Soweit mein Gedanke dahinter 😀

          Ich habe gerade nur etwas die Doku von IOBroker überflogen, und bin erstmal von der Komplexität erschlagen. Das ganze Ding basiert auf Node.js - was wichtig ist zu wissen. Das ist auch eine JavaScript-Umgebung, aber es ist kein Browser. D.h. es gibt dort ganz andere Programmierschnittstellen. Der JavaScript-Kern ist gleich, aber es gibt eine Menge drumherum, und das Drumherum ist im Browser und in Node sehr verschieden. Unser Wiki bezieht sich nur auf JavaScript im Browser. Der /JavaScript-Teil geht vor allem auf JavaScript ein, aber es kann auch sein, dass einige Beispiele Dinge tun, die nur im Browser funktionieren.

          Das ist eine sehr wichtige Information für mich, vielen Dank auch dafür! Ich werde mir das im Hinterkopf behalten 😉

          Möglicherweise - soweit habe ich die Doku nicht studiert - kann man im IOBroker auch Script bereitstellen, das auf den von IOBroker ausgelieferten HTML Seiten abläuft. Du musst also immer genau aufpassen, für welche Plattform Du programmierst.

          Ja, soweit ich das verstehe, ist die Plattform immer die IOBroker Plattform, für die ich Scripte erstellen kann und nicht für den "Browser", wie es vermutlich üblicherweise in diesem Forum diskutiert wird. Aber JavaScript als solches ist in seiner Grundlage bei beiden Umgebungen (mehr oder weniger) dasselbe.

          Diese "getSate"-Funktion (die vermutlich getState heißt) - ist das etwas von Dir oder von IOBroker?

          Der Schreibfehler ist von mir, die Funktion von IOBroker 😉

          Ich danke dir für deine konstruktive und sehr hilfreiche Lektion.

          LG Roli

          1. Hallo ROGA,

            die Doku von IOBroker verwirrt mich. Ich habe PV, demnächst auch eine Wärmepumpe, das Ding interessiert mich daher demnächst wohl auch.

            Aber es gibt verschiedene Dokus:

            Die zweite Doku scheint die Altfassung zu sein, dafür enthält sie aber eine Seite "Einführung in JavaScript". Die getState()-Funktion finde ich hingegen nur in der ersten Doku beschrieben. Womit arbeitest Du?

            Anyroad - mein Beispiel 2, wo das Objekt Stück für Stück zusammengesetzt wird, ist in dem Fall interessant, wenn Du die Information von verschiedenen Stellen zusammensuchen musst und dafür eventuell auch Funktionen aufrufst. In dem Fall kannst Du nicht ein einzelnes Objektliteral verwenden (oder müsstest die Daten erstmal in Variablen sammeln und die dann im Objektliteral benutzen, was Blödsinn wäre).

            Man kann das aber auch mischen.

            const pvData = {
               produktion: liesProduktion(),
               verbrauch: liesVerbrauch(),
               speicher: {
                  batterien: [],
                  inverter: liesInverter(),
               },
            }
            for (let i=0; i<=3; i++)
               pvData.speicher.batterien[i] = liesBatterieStatus(i);
            }
            
            // ... anderswo
            function liesVerbrauch() {
               const verbrauch = {
                  haus: liesVerbrauchHaus(),
                  wallbox: 0,
               };
               if (dings != bums) {
                  verbrauch.wallbox = 4711; 
               }
            }
            

            und so weiter, das ist eine reine Phantasielösung, zeigt aber, was man tun kann. liesProduktion, liesVerbrauch etc sind Funktionen, die einen einfachen Wert oder ein Objekt mit weiteren Eigenschaften zurückgeben. In speicher.batterien wird erstmal nur ein leeres Array abgelegt und dieses dann in einer Schleife über alle Batterien befüllt. liesBatterieStatus kümmert sich dann nur um den Status der i-ten Batterie (ob Du die von 0-3 oder von 1-4 nummerierst, ist Deine Entscheidung).

            liesVerbrauch ist eine beispielhafte Funktion, in der ein Teilobjekt für pvData zusammengebaut wird. Der Hausverbrauch kommt aus einer weiteren Funktion, und die Wallboxdaten hängen an irgendeiner Programmlogik. Hier sind dann Einzelzuweisungen nötig. Da ich dein Umfeld nicht kenne, kann ich natürlich nur solche Dummybeispiele machen und nichts aufschreiben, was für Dich spezifisch passt. Ich müsste erstmal den IOBroker an meinen SENEC-Speicher[1] anschließen, um darüber mehr zu lernen 😀

            Da gibt's viele Möglichkeiten, wie man das mixen und rühren kann.

            Rolf

            --
            sumpsi - posui - obstruxi

            1. Yup, ich bin einer von denen, die das für eine gute Idee hielten und ein Jahr lang auf 70% limitiert wurden, aber die getauschten LiFeP-Akkus funktionieren jetzt störungsfrei. ↩︎

            1. Hallo Rolf,

              ... Womit arbeitest Du?

              Eigentlich nutze ich ersteres mit Verweis auf Github. Dort scheint mir alles mehr oder weniger akurat zu sein.

              Anyroad - mein Beispiel 2, wo das Objekt Stück für Stück zusammengesetzt wird, ist in dem Fall interessant, wenn Du die Information von verschiedenen Stellen zusammensuchen musst und dafür eventuell auch Funktionen aufrufst. In dem Fall kannst Du nicht ein einzelnes Objektliteral verwenden (oder müsstest die Daten erstmal in Variablen sammeln und die dann im Objektliteral benutzen, was Blödsinn wäre).

              Für meinen Fall (denke ich zumindest) sollte sich das Objekt alle seine Daten einfach nur aus dem Adapter holen. Der Adapter liefert alle nur erdenklichen Datenpunkte, sodass ich diese nicht weiter zusammensuchen muss. Somit würde ich also mein Objekt aus nur einem Objektliteral zusammenbauen, sozusagen aus einem Guss geschmiedet. Die Idee zu folgendem Beispiel stammt netterweise von Felix Riesterer (ich hoffe, ich hab seine Erläuterungen richtig interpretiert). Als erstes konstruiere ich mir mein PVA-Objekt das wiederum mit anderen Objekten ausgestattet wird. Diese erhalten dann Eigenschaftswerte, die erst einmal leere Werte enthalten. Das PVA-Objekt bekommt nun noch eine Methode, mit der es möglich wird, die Eigenschaftswerte mit realen Daten aus dem Adapter zu füllen.

              const MeinPVAObjekt = {
                // die Batterie als Objekt mit seinen Eigenschaften
                Batterie: {
                  Status: null,
                  LadungEntladung: null
                },
                // die Solarpanels als Objekt mit seinen Eigenschaften
                Panels: {
                  AktuelleLeistung: null
                },
                // der Wechselrichter als Objekt mit seinen Eigenschaften
                Wechselrichter: {
                  NetzBezugEinspeisung: null
                },
                
                Haus: {
                  Verbrauch: null
                },
                // Methode, um die Daten in das Objekt zu laden
                DatenEinlesen: function() {
                  this.Batterie.Status = getState('sun2000.0.collected.SOC').val;
                  this.Batterie.LadungEntladung = getState('sun2000.0.collected.chargeDischargePower').val;
                  this.Panels.AktuelleLeistung = getState('sun2000.0.collected.inputPower').val;
                  this.Wechselrichter.NetzBezugEinspeisung = getState('sun2000.0.meter.activePower').val;
                  this.Haus.Verbrauch = getState('sun2000.0.collected.houseConsumption').val;
                }
              };
              
              

              Das allerdings sollte so gebaut sein, dass ich dann in meinem eigentlichen JavaScript (wo ich dann meine Steuerung programmiere) eine Variable instanzieren und damit dann arbeiten kann.

              Wie das aber geht, entzieht sich wieder einmal vollkommen meinen Kenntnissen 😟

              Eventuell so ungefähr?

              const PVA = MeinPVAObjekt;
              
              PVA.DatenEinlesen();
              
              console.log("Batterie-Status: " + PVA.Batterie.Status);
              console.log("Batterie Ladung/Entladung: " + PVA.Batterie.LadungEntladung);
              console.log("Aktueller Solar-Ertrag: " + PVA.Panels.AktuelleLeistung);
              console.log("Netz Bezug/Einspeisung: " + PVA.Wechselrichter.NetzBezugEinspeisung);
              console.log("Hausverbrauch: " + PVA.Haus.Verbrauch);
              

              Möglicherweise habe ich mich mit diesem Beispiel nicht verständlich genug ausgedrückt, falls ja, bitte einfach nachfragen.

              Vielen Dank schon mal im Voraus für die konstruktive Hilfeleistung.

              LG Roli

              1. Eine Abstraktion, die sowohl generisch genug, als auch wartbar/skalierbar und sicher ist, würde höchstwahrscheinlich klassenbasiert sein, wobei zusätzlich noch auf Vererbung und Datenkapselung zurückgegriffen werden wird.

                Die Verwendung von bspw. ... getState('sun2000.0.collected.chargeDischargePower').val ... zeigt, dass solche Entitäten schon im System existieren. Die eigentliche Aufgabe scheint demzufolge eher darin zu bestehen, eine flexible, sichere und skalierbare Abstraktion und Implementierung für ein reines Mapping zur Datanausgabe zu erschaffen.

                Das folgende Code-Beispiel benutzt Features des JavaScript Klassensyntax, um das eigentliche Zieltemplate zu erstellen; die PVA-Klasse zur Erschaffung von PVA-Instanzen.

                Diese Klasse nutzt die Technik der Aggregation, um die Abstraktion einer Photovoltaikanlage (PVA) vollständig abzubilden.

                Und um zu gewährleisten, dass die zu aggregierenden Entitäten, wie z.b. Batterie, Panele, Wechselrichter oder Haus, einerseits öffentlich zugänglich, aber auch schreibgeschützt sind, werden private Felder mit den dazugehörigen getter Methoden implementiert.

                Jede Entität wird ebenfalls durch ihre eigene Klassenabstraktion abgebildet. Auch hier wird überall mit (schreib)geschützten Eigenschaften gearbeitet. Darüber hinaus beerben diese Entitäten jeweils einunddieselbe BrokerEntity-Basisklasse.

                Über die Datensicherheit hinaus gewährleistet dieser zweiteilige Ansatz Skalier- und Wartbarkeit solcher Entitäten; aber warum?

                Dreh- und Angelpunkt ist die schon oben erwähnte getStatus-Function der IO-Broker API. Der erwartete Parameter beschreibt den keypath in der Baumstruktur eines Objekts welcher zu einem Objektwert führt.

                Die Basisklasse verwaltet die keypath-Wurzel und die getStatus-Funktion, welche bei der Instantiierung sowohl der PVA-Klasse als auch von konkreten PVA-Entitäten in den Konstruktor gereicht werden, wohingegen die Implementierung einer PVA-Entität sich für jede Entität-spezifische Eigenschaft nur um die korrekte super delegation vom Basisklassen-getStatus kümmern muss. An genau dieser Stelle wird/ist das Mapping über den richtig zusammengestellten keypath abgeschlossen/beendet.

                Nehmen wir an, dass ein PVA-Instanz jetzt folgendermassen erzeugt wurde ... const pva = new PVA('sun2000', 0, getStatus); ... dann lassen sich alle Werte folgendermassen erreichen ...

                pva.keypath;
                pva.name;
                pva.idx;
                
                
                pva.battery.keypath;
                
                pva.battery.soc;
                pva.battery.soc.val;
                
                pva.battery.chargeDischargePower;
                pva.battery.chargeDischargePower.val;
                
                
                pva.panels.keypath;
                
                pva.panels.inputPower;
                pva.panels.inputPower.val;
                
                
                pva.meter.keypath;
                
                pva.meter.activePower;
                pva.meter.activePower.val;
                
                
                pva.house.keypath;
                
                pva.house.consumption;
                pva.house.consumption.val;
                

                Da jeder Wert unmittelbar aufgerufen werden kann, ist die ursprünglich angedachte/vorgesehene Methode DatenEinlesen hinfällig. Sinnvoll hingegen ist es, eine eigene valueOf-Methode zu implementieren. Dort hat man die Kontrolle über die Ausgabe einer selbst definierten Datenstruktur.

                Der folgende Beispielcode dient sowohl zur Veranschaulichung der obigen Prosa als auch als "Proof of Concept". Ein kleiner Test ist ebenfalls dabei. Man kann den gesamten Code in jede beliebige JavaScript-Konsole kippen und ausführen ...

                class BrokerEntity {
                
                  #keypath;
                  #getStatus
                
                  constructor(keypath, getStatus) {
                    this.#keypath = keypath;
                    this.#getStatus = getStatus;
                  }
                  get keypath() {
                    return this.#keypath;
                  }
                
                  getStatus(keypath) {
                    return this.#getStatus(keypath);
                  }
                }
                
                class PvaBattery extends BrokerEntity {
                
                  constructor(...args) {
                    super(...args);
                  }
                  get keypath() {
                    return `${ super.keypath }.collected`;
                  }
                
                  get soc() {
                    return super.getStatus(`${ this.keypath }.SOC`);
                  }
                  get chargeDischargePower() {
                    return super.getStatus(`${ this.keypath }.chargeDischargePower`);
                  }
                }
                class PvaPanels extends BrokerEntity {
                
                  constructor(...args) {
                    super(...args);
                  }
                  get keypath() {
                    return `${ super.keypath }.collected`;
                  }
                
                  get inputPower() {
                    return super.getStatus(`${ this.keypath }.inputPower`);
                  }
                }
                class PvaMeter extends BrokerEntity {
                
                  constructor(...args) {
                    super(...args);
                  }
                  get keypath() {
                    return `${ super.keypath }.meter`;
                  }
                
                  get activePower() {
                    return super.getStatus(`${ this.keypath }.activePower`);
                  }
                }
                class PvaHouse extends BrokerEntity {
                
                  constructor(...args) {
                    super(...args);
                  }
                  get keypath() {
                    return `${ super.keypath }.collected`;
                  }
                
                  get consumption() {
                    return super.getStatus(`${ this.keypath }.houseConsumption`);
                  }
                }
                
                class PVA {
                
                  #name;
                  #idx;
                
                  #battery;
                  #panels;
                  #meter;
                  #house;
                
                  constructor(name, idx, getStatus) {
                    const keypath = `${ name }.${ idx }`;
                
                    this.#name = name;
                    this.#idx = idx;
                
                    this.#battery = new PvaBattery(keypath, getStatus);
                    this.#panels = new PvaPanels(keypath, getStatus);
                    this.#meter = new PvaMeter(keypath, getStatus);
                    this.#house = new PvaHouse(keypath, getStatus);
                  }
                  get keypath() {
                    return `${ this.name }.${ this.idx }`;
                  }
                  get name() { return this.#name }
                  get idx() { return this.#idx; }
                
                  get battery() { return this.#battery; }
                  get panels() { return this.#panels; }
                  get meter() { return this.#meter; }
                  get house() { return this.#house; }
                
                  valueOf() {
                    return {
                
                      batterie: {
                        status: this.battery.soc.val,
                        ladung_entladung: this.battery.chargeDischargePower.val,
                      },
                      panele: {
                        aktuelle_leistung: this.panels.inputPower.val,
                      },
                      wechselrichter: {
                        netzbezug_einspeisung: this.meter.activePower.val,
                      },
                      haus: {
                        verbrauch: this.house.consumption.val,
                      }
                    }
                  }
                }
                
                /*
                 *  test case of the above implemented architecture.
                 */
                const pvaStatusMock = new Map([
                  [ 'sun2000.0.collected.SOC', { val: '80%' } ],
                  [ 'sun2000.0.collected.chargeDischargePower', { val: '2.5 / 2.3 kW' } ],
                  [ 'sun2000.0.collected.inputPower', { val: '2 kWp' } ],
                  [ 'sun2000.0.meter.activePower', { val: '0.7 kWh' } ],
                  [ 'sun2000.0.collected.houseConsumption', { val: '0.95 kWh' } ],
                ]);
                function getStatusMock(keypath) {
                  return pvaStatusMock.get(keypath);
                }
                
                const pva = new PVA('sun2000', 0, getStatusMock);
                
                console.log('pva meta-data ...', {
                  name: pva.name,
                  idx: pva.idx,
                  keypath: pva.keypath,
                });
                console.log('current pva status ...', pva.valueOf());
                
      3. Lieber ROGA,

        PVA.Batterie.Status = getSate('sun2000.0.collected.SOC').val;
        PVA.Batterie.LadungEntladung = getSate('sun2000.0.collected.chargeDischargePower').val;
        

        aus Sicht von JavaScript greifst Du mit PVA.Batterie auf eine Eigenschaft namens Batterie des in der Variablen PVA gespeicherten Objektes zu. Diese Eigenschaft muss natürlich existieren, weil Du sie in diesem Objekt angelegt hast:

        const PVA = new Object();
        PVA.Batterie = new Object();
        PVA.Batterie.Status = "irgendein Wert von irgendwo her";
        PVA.Batterie.LadungEntladung = "irgendein anderer Wert von irgendwo her";
        

        In JavaScript gibt es verschiedene Schreibweisen, wie man ein Objekt definieren kann. Im Vorliegenden Fall braucht es das PVA-Objekt nur einmal. Da lohnt es sich nicht, eine Art Blaupause dafür zu machen, um von dieser Art Objekt mehrere Instanzen zu erzeugen. Oder hast Du vor, damit einmal mehrere Anlagen auf einer Seite zu steuern?

        const PVA = {
          // In den geschweiftern Klammern steht das Objekt als Literal notiert.
          Batterie: {
            // das Batterie-Objekt seinerseits als Literal
            Status: null,
            LadungEntladung: null
          },
          // nun eine Methode, also eine Funktion, die dem Objekt PVA „gehört“:
          BatterieEinlesen: function () {
            this.Batterie.Status = getState('sun2000.0.collected.SOC').val;
            this.Batterie.LadungEntladung = getState('sun2000.0.collected.chargeDischargePower').val;
          }
        };
        
        console.log(PVA); // die Batterie hat leere Werte (jeweils null)
        PVA.BatterieEinlesen();
        console.log(PVA); // die Batterie sollte nun die ermittelten Werte haben
        

        Liebe Grüße

        Felix Riesterer

        1. Lieber Felix,

          aus Sicht von JavaScript greifst Du mit PVA.Batterie auf eine Eigenschaft namens Batterie des in der Variablen PVA gespeicherten Objektes zu. Diese Eigenschaft muss natürlich existieren, weil Du sie in diesem Objekt angelegt hast:

          Auch dein Erklärungsansatz hat mir für das Verständnis von JavaScript Objekten sehr weitergeholfen.

          const PVA = new Object();
          PVA.Batterie = new Object();
          PVA.Batterie.Status = "irgendein Wert von irgendwo her";
          PVA.Batterie.LadungEntladung = "irgendein anderer Wert von irgendwo her";
          

          In JavaScript gibt es verschiedene Schreibweisen, wie man ein Objekt definieren kann.

          Das wurde mir von Rolf im vorangehenden Thread ebenso erläutert, was nun von dir auch bestätigt wird.

          Im Vorliegenden Fall braucht es das PVA-Objekt nur einmal. Da lohnt es sich nicht, eine Art Blaupause dafür zu machen, um von dieser Art Objekt mehrere Instanzen zu erzeugen. Oder hast Du vor, damit einmal mehrere Anlagen auf einer Seite zu steuern?

          Hier habe ich wieder einen kleine Knoten in meiner Verstandsleitung 😉 Was genau meinst du mit "eine Art Blaupause"? Wie kann ich davon Instanzen machen/haben? Ich definiere das Objekt doch nur einmal, oder?

          const PVA = {
            // In den geschweiftern Klammern steht das Objekt als Literal notiert.
            Batterie: {
              // das Batterie-Objekt seinerseits als Literal
              Status: null,
              LadungEntladung: null
            },
            // nun eine Methode, also eine Funktion, die dem Objekt PVA „gehört“:
            BatterieEinlesen: function () {
              this.Batterie.Status = getState('sun2000.0.collected.SOC').val;
              this.Batterie.LadungEntladung = getState('sun2000.0.collected.chargeDischargePower').val;
            }
          };
          
          console.log(PVA); // die Batterie hat leere Werte (jeweils null)
          PVA.BatterieEinlesen();
          console.log(PVA); // die Batterie sollte nun die ermittelten Werte haben
          

          Dieses Beispiel gefällt mir sehr gut! Hier sehe ich potential, wie ich das Objekt dynamisch so gestalten kann, dass ich damit immer die aktuellen Daten bei Bedarf abrufen könnte. Würde ich das Objekt nur einmal initialisieren ohne diese Methode einzubauen, dann wären die Daten nur statisch im Objekt und somit nach einigen Minuten schon nicht mehr aktuell. Sehe ich das so richtig? Ich könnte das Objekt auch so bauen, dass es nur eine Methode gäbe, die aber sämtliche, sich darin befindlichen Objekte und Eigenschaften mit aktuellen Daten abfüllen würde, oder?

          Dieser Ansatz finde ich sehr interessant, vielen Dank für diesen Denkanstoss!

          LG Roli

          1. Lieber ROGA,

            Was genau meinst du mit "eine Art Blaupause"?

            in der objektorientierten Programmierung kann man eine Art Bauplan von einem Objekt definieren, mit dem man dann viele Instanzen davon erzeugen kann.

            Wie kann ich davon Instanzen machen/haben? Ich definiere das Objekt doch nur einmal, oder?

            Wenn man ein Objekt nur ein einziges Mal benötigt, spricht man auch von einem singleton. Das kann man gut mit einem Objektliteral notieren. Aber man könnte auch eine Klasse definieren und dann nur eine Instanz davon erzeugen. Wenn Du Dir eine dynamische Seite für den Browser machen möchtest, wirst Du Fällen begegnen, in denen man mehrere Instanzen von einem Objekt braucht.

            Würde ich das Objekt nur einmal initialisieren ohne diese Methode einzubauen, dann wären die Daten nur statisch im Objekt und somit nach einigen Minuten schon nicht mehr aktuell. Sehe ich das so richtig?

            Richtig. Es braucht einen wie auch immer gearteten Mechanismus, der die Eigenschaften von PVA.Batterie neu befüllen kann, sonst verbleiben sie unverändert so, wie sie beim Erzeugen von PVA eingetragen wurden. Dieser Mechanismus kann irgendeine Funktion irgendwo sein. Um Dir aber den Begriff Methode im objektorientierten Programmieren in JavaScript beispielhaft vorzuführen, habe ich Dein Objekt um eine solche erweitert. Damit „kann“ Dein PVA-Objekt seine Batteriedaten selbst holen und in seine passenden Eigenschaften setzen.

            Ich könnte das Objekt auch so bauen, dass es nur eine Methode gäbe, die aber sämtliche, sich darin befindlichen Objekte und Eigenschaften mit aktuellen Daten abfüllen würde, oder?

            Selbstverständlich. Wie Du Dein Objekt bauen willst, und was es dann wie tun soll, das modellierst Du ganz nach Deinen Vorstellungen. Da Du in Deinem Anfangsbeispiel nur dieses eine Unterobjekt Batterie gezeigt hast, habe ich dafür eine passende Methode geschrieben. Aber wenn Du noch weitere Unterobjekte vorsiehst, dann sollte die Methode vielleicht nicht an PVA hängen, sondern an PVA.Batterie:

            const PVA = {
              // In den geschweiftern Klammern steht das Objekt als Literal notiert.
              Batterie: {
                // das Batterie-Objekt seinerseits als Literal
                Status: null,
                LadungEntladung: null,
                // nun eine Methode, also eine Funktion, die dem Objekt Batterie „gehört“:
                loadData: function () {
                  this.Status = getState('sun2000.0.collected.SOC').val;
                  this.LadungEntladung = getState('sun2000.0.collected.chargeDischargePower').val;
                }
              }
            };
            
            PVA.Batterie.loadData();
            

            Wenn man dann eine Liste von Unterobjekten hat, kann man die der Reihe nach durchgehen und prüfen, ob sie eine Methode namens loadData haben, um diese dann auszuführen:

            Object.keys(PVA).forEach(eigenschaftsname => {
              if ("function" == typeof PVA[eigenschaftsname].loadData) {
                PVA[eigenschaftsname].loadData();
              }
            });
            

            Diese Funktionalität sollte vielleicht das PVA-Objekt selbst als Methode haben:

            const PVA = {
              // In den geschweiftern Klammern steht das Objekt als Literal notiert.
              Batterie: {
                // das Batterie-Objekt seinerseits als Literal
                Status: null,
                LadungEntladung: null,
                // nun eine Methode, also eine Funktion, die dem Objekt Batterie „gehört“:
                loadData: function () {
                  this.Status = getState('sun2000.0.collected.SOC').val;
                  this.LadungEntladung = getState('sun2000.0.collected.chargeDischargePower').val;
                }
              },
              QuatschMitSosse: {
                Geschmack: null,
                Farbe: null,
                Sinn: null,
                loadData: function () {
                  console.log("Was genau kommt da woher ...?");
                }
              },
              // Methode zum Aktualisieren der Daten in den Unterobjekten
              update: function () {
                Object.keys(this).forEach(name => {
                  if ("function" == typeof this[name].loadData) {
                    this[name].loadData();
                  }
                });
              }
            };
            
            PVA.update();
            

            Spätestens jetzt wird die Sache unübersichtlich. Jetzt kommt Rolfs Idee mit dem Aufbauen des Objektes Stück-für-Stück zum Tragen. Man kann nun das Batterie-Objekt, sowie das QuatschMitSosse-Objekt in eigenen Programmteilen bauen, damit deren Funktionalität individuell abgebildet werden kann. Dann hat man kein solches Monster an Objekt-Literal, sondern mehrere kleinere.

            Mir ist klar, dass ich Dich gerade mit vielen Details überfrachte, darunter z.B. das magische Schlüsselwort this, oder die Verwendung der Object-Klasse und deren keys-Methode, dazu das Iterieren über Elemente eines Arrays (keys gibt ein solches zurück) mittels der Array-Methode forEach. Und dann natürlich noch das Übergeben einer Funktion als Argument in einem Funktionsaufruf, dazu noch als Pfeilfunktion... aber Du kannst Dir das ja in Ruhe alles der Reihe nach anschauen.

            Liebe Grüße

            Felix Riesterer

            1. Lieber Felix,

              Mir ist klar, dass ich Dich gerade mit vielen Details überfrachte, darunter >z.B. das magische Schlüsselwort this... ... aber Du kannst Dir das ja in Ruhe alles der Reihe nach anschauen.

              bis zur Hälfte konnte ich deinen Ausführungen noch sehr gut folgen, doch dann wurde ich erbarmungslos erschlagen! 😉

              Ich muss mir das wohl noch einige Male durchlesen, damit ich deine Erklärungen und damit auch die zweite Hälfte wirklich verstehe.

              Du und Rolf, ihr habt mir bis hierher sehr geholfen, dafür vielen Dank!

              Ich melde mich ganz bestimmt noch einmal wieder, wenn ich den Pfad im Dschungel wieder gefunden habe. Aktuell stehe ich jetzt ganz schön Abseits 😕

              LG Roli

              1. Hallo ROGA,

                nachdem Felix Dich jetzt schon auf die Konstruktoren und die class Syntax gestupst hat, sollte man vielleicht auch entsprechende Beispiele machen.

                Denn: Objekte so anzulegen, wie wir das hier bisher getan haben, ist JavaScript von vor 15 Jahren. Heute gelingt das besser mit class Syntax, das kann Node.js auch.

                In solchen Fällen ist es aber auch hilfreich, die Klassendefinition in einer eigenen Quellcode-Datei zu haben, und JETZT kommt noch das Modulkonzept von Node.js hinzu. Dumm ist: aus historischen Gründen gibt es 2 solche Konzepte. Aber wenn man sich die IOBroker Doku anschaut, will er das ältere verwenden - CommonJS, mit der require()-Funktion.

                // Solar Klasse in solar.js
                module.exports = class Solar {
                   // das kriegen wir später
                }
                

                Das, was Du an module.exports zuweist, wird von der require-Funktion zurückgegeben.

                Im Hauptprogramm steht dann nur:

                const Solar = require("./solar.js");
                
                const pvDaten = new Solar();
                // alles fertig, pvDaten kann verwendet werden
                
                // später: Stand der pvDaten aktualisieren
                pvDaten.loadData();
                

                Die Solar-Klasse erstellen wir so, dass sie sich alle Daten selbst holt. Dafür gibt es die constructor-Methode, die JavaScript aufruft, sobald das Objekt mit new erstellt wurde. Man kann beim new-Aufruf auch Parameter angeben, die werden dann dem Konstruktor übergeben. Sowas kann für die Batterie interessant sein, wenn man mehrere davon hat.

                // Solar Klasse in solar.js
                module.exports = class Solar {
                   constructor() {
                      this.Batterien = [
                         new Batterie(0),
                         new Batterie(1)
                      ];
                      this.PV = new PV();
                   }
                   loadData() {
                      for (let batt of this.Batterien)
                         batt.loadData();
                
                      this.PV.loadData();
                   }
                }
                
                class Batterie {
                   constructor(batterieNr) {
                      this.myNumber = batterieNr;
                      loadData();
                   }
                   loadData() {
                      const prefix = `sun2000.${this.myNumber}.collected.`;
                      this.Status = getState(prefix + 'SOC`).val;
                      this.LadungEntladung = getState(prefix + 'chargeDischargePower').val;
                      this.Dies = getState(...);
                      this.Das = getState(...);
                    }
                }
                
                class PV {
                   constructor() {
                      this.loadData();
                   }
                
                   loadData() {
                      this.Leistung = getState('sun2000.generatedPower').val;
                         // oder wie auch immer das heißen mag
                   }
                }
                

                Nur die Klasse Solar wird exportiert, die Klassen Batterie und PV können intern im Modul bleiben, weil Du von diesen Klassen außerhalb des Moduls keine Objekt erstellen musst. Darum kümmert sich die Solar-Klasse.

                Die ist so gebaut, dass sie beim Erzeugen die Unterobjekte anlegt. Und DIE sind wiederum so gebaut, dass sie sich beim Erzeugen ihre Daten selbst beschaffen. Für den Update der Daten ruft man auf dem solar-Objekt loadData() auf und der delegiert das an seine Unterobjekte. D.h. wenn Du weitere Unterobjekte ergänzt, musst Du auch loadData erweitern.

                Ich habe für die Batterien ein Array gemacht. Falls Du nur eine hast, kannst Du auf das Array verzichten. Wenn es mehrere Batterien gibt, hast Du mehrere Batterie-Objekte und übergibst jedem seine Nummer. Es merkt sie sich in der batterieNr Eigenschaft, so dass dann jedes Batterieobjekt auf seine Batterienummer zugreift.

                this steht innerhalb einer Methode für das Objekt, auf dem die Methode aufgerufen wurde.

                Ein String, der mit ```-Zeichen (Backticks) gebildet wird, ist ein Template-Literal, ähnlich dem String-Parsing in PHP.

                Auf weitere OOP-Feinheiten wie private Eigenschaften oder getter verzichte ich erstmal, sonst sprengt es Dir die Birne vollständig 😉. Abstraktion ist schön und gut, aber als Einsteiger muss man es noch nachvollziehen können.

                Rolf

                --
                sumpsi - posui - obstruxi
                1. Lieber Rolf,

                     loadData() {
                        for (let batt of this.Batterien)
                           batt.loadData();
                  
                        this.PV.loadData();
                     }
                  }
                  

                  diese loadData-Methode der Solar-Klasse „weiß“, dass ihre Batterie-Objekte eine Methode loadData haben, sowie dass das auch ihr PV-Objekt eine solche hat. Das kann man so machen und fährt dabei auch sehr gut. Der klitzekleine Nachteil ist nur der, dass man Solar.loadData immer wieder anpassen muss, wenn man eine neue Objektart entwirft, um sie als Eigenschaft von Solar zu verwenden.

                  Ob das von Bedeutung ist (oder werden wird), kann ich unmöglich abschätzen, weil ich nicht weiß, wofür @ROGA die Klasse Solar denn tatsächlich verwenden möchte. Bis jetzt dient sie lediglich der Aggregation der Daten von diversen Komponenten seine PV-Anlage. Aber was soll mit der Solar-Instanz später geschehen? „Nur“ eine Ausgabe auf die Konsole? Oder eine visuelle Aufbereitung für das Anzeigen in einem Browser? Das wäre jetzt wichtig zu wissen, um vielleicht Batterie und PV als eine Art Komponenten-Klasse zu bauen, die dann von der Elternklasse Solar auf eine vereinheitlichte Art angesprochen werden kann, um die Ausgabe besser erstellen zu können.

                  Liebe Grüße

                  Felix Riesterer

                  1. Hallo Felix,

                    vereinheitlicht ist es ja schon: loadData() 😉. Ich hätte es updateData() genannt, aber das ist wurscht.

                    Solar, Batterie und PV sind konzeptionell Adapterklassen für das IOBroker-Statesystem. Um Präsentation haben sie sich demnach nicht zu scheren. Dass sie sich um ihre Datenbeschaffung kümmern, ist ihrer Adapternatur geschuldet und für ein ordentliches OO-Design bereits grenzwertig. Für die Präsentation erstellt man ViewModel-Klassen, die die Brücke zwischen IOBroker-Präsentation und Solar-Modell schlagen.

                    Im Übrigen haben wir es mit einem JS-Einsteiger (m/w/d) zu tun, den man nicht mit Objektabstraktionen überladen[1] sollte.

                    Rolf

                    --
                    sumpsi - posui - obstruxi

                    1. pun definitely intended ↩︎

                2. Lieber Rolf,

                  Auf weitere OOP-Feinheiten wie private Eigenschaften oder getter verzichte ich erstmal, sonst sprengt es Dir die Birne vollständig 😉. Abstraktion ist schön und gut, aber als Einsteiger muss man es noch nachvollziehen können.

                  Nachdem mich Felix mit seinen Erläuterungen schon erschlagen hat (im positiven Sinne), hast du mich gleich zwei Mal ausgenockt... das muss ich jetzt erst einmal in Ruhe verdauen! 😀

                  Was ich aber auf Anhieb glaube zu erkennen ist, dass du in deinen Beispielen genau auf die Thematik eingehst, die auf meine Fragen abzielen.

                  Ich denke, ich bin mit deinen sehr guten Beispielen jetzt erst einmal etwas beschäftigt und würde mich freuen, wenn ich mich bei Unklarheiten wieder bei dir melden dürfte.

                  Danke nochmals, euch beiden für die Geduld und Zeit, die ihr euch genommen habt!

                  LG Roli

                  1. Hallo ROGA,

                    wieder bei dir melden dürfte.

                    Nein, darfst Du nicht. Wir helfen normalerweise nicht im stillen Postkämmerlein.

                    Aber Du darfst Dich gerne im Forum melden 😀

                    Wenn's gar nicht geht, komm Mittwochs zum Selfhtml-Stammtisch auf Discord, da kann man dann Dinge live diskutieren und Du kannst auch Code vorzeigen.

                    Rolf

                    --
                    sumpsi - posui - obstruxi
                    1. Lieber Rolf,

                      Aber Du darfst Dich gerne im Forum melden 😀

                      klar doch, das meinte ich eigentlich auch damit 😀

                      LG Roli

            2. Lieber Felix,

              Ich fange langsam an, das Konstrukt zu verstehen und habe gerade für mich gedacht: "Ja, so macht das erstellen eines Objekts Sinn, so möchte ich es bauen" und du schreibst dann:

              Spätestens jetzt wird die Sache unübersichtlich.

              Für mich macht das Objekt in deinem Beispiel so absolut Sinn und ich würde es auch so bauen wollen. Sehe ich hier einfach nicht über den Tellerrand hinaus? Du und Rolf, ihr scheint euch hier ziemlich einig zu sein, was diese Problematik betrifft. Für mich ist es noch etwas undurchsichtig, weshalb ich mir die Frage stelle: Wieso wird's "unübersichtlich"?

              Des Weiteren ...

               // Methode zum Aktualisieren der Daten in den Unterobjekten
               update: function () {
                 Object.keys(this).forEach(name => {
                   if ("function" == typeof this[name].loadData) {
                     this[name].loadData();
                   }
                 });
              
              

              Hier beiss ich mir an deinem Beispiel gerade die Zähne aus. Ich verstehe zwar, was diese Methode als Ergebnis liefert bzw. was sie bewirken soll, doch ich verstehe noch nicht wirklich, wie das im wesentlichen geschieht. Ich versuche das mal hier mit meinen eigenen Gedanken nachzuvollziehen:

              Die Methode "update" ruft eine Funktion ohne Namen auf "update: function()". Dann folgt der Konstrukt dieser Funktion worin ein Objekt aufgerufen wird, das die Methode ForEach() besitzt -> "Object.keys(this).forEach()". Sollte ich das soweit richtig interpretiert haben, so habe ich es leider noch nicht entschlüsseln können, was "Object.keys() genau macht. Ich erahne nur, dass hier möglicherweise mit "this" (was geschätzt wohl das Objekt PVA repräsentiert) das gesamte PVA-Objekt nach Methoden mit der Bezeichnung "loadData" durchsucht wird. Wenn eine solche Methode gefunden wurde, wird diese dann mittels this[name].loadData() aufgerufen und somit ausgeführt. Aber wirklich sicher bin ich mir hier nicht.

              Vielleicht magst du (oder jemand anders) meinem Gedankengang etwas auf die Sprünge helfen. Schon mal Danke im Voraus dafür ....

              LG Roli

              1. Lieber ROGA,

                Ich fange langsam an, das Konstrukt zu verstehen und habe gerade für mich gedacht: "Ja, so macht das erstellen eines Objekts Sinn, so möchte ich es bauen" und du schreibst dann:

                :-) Fortschritt!

                Spätestens jetzt wird die Sache unübersichtlich.

                Für mich macht das Objekt in deinem Beispiel so absolut Sinn und ich würde es auch so bauen wollen. Sehe ich hier einfach nicht über den Tellerrand hinaus?

                Damit meinte ich nicht das Objekt selbst, sondern den Code. Um eine Objektstruktur dieser Art zu bauen, würde ich gerne die Struktur des Batterie-Objektes als eigenständiges Stück Code haben wollen, welches nur die Batterie selbst beschreibt. Ihre Eigenschaften (Werte) und Methoden (Funktionen). Das Zusammenfügen zum PV-Objekt bedient sich dann an diesem Code.

                Beispiel mit der class-Schreibweise:

                class Batterie {
                
                  constructor (index) {
                    this.index = index;
                  }
                
                  loadData () {
                    this.Status = getState('sun2000.' + this.index + '.collected.SOC').val;
                    this.LadungEntladung = getState('sun2000.' + this.index + '.collected.chargeDischargePower').val;
                  }
                
                  index = 0;
                  Status = null;
                  LadungEntladung = null;
                }
                
                class PVA {
                
                  Batterie = null;
                  QuatschMitSosse = null;
                
                  update () {
                   Object.keys(this).forEach(name => {
                     if ("function" == typeof this[name].loadData) {
                       this[name].loadData();
                     }
                   });
                  }
                }
                
                const myPVA = new PVA();
                myPVA.Batterie = new Batterie(0); // 0 bedeutet sun2000.0 für erste Batterie
                

                Hier werden zwei Klassen definiert, in denen die Blaupause für das jeweilige Objekt stehen. Mit dem Schlüsselwort new kann damit eine neue Objektinstanz erzeugt werden (siehe letzte beiden Zeilen). Anstatt das verschachtelte Gesamt-Objekt PV zu notieren, werden hier nur die jeweiligen Einzelobjekte definiert, um sie am Ende zusammenzusetzen. Das kann die Übersicht erhöhen.

                 // Methode zum Aktualisieren der Daten in den Unterobjekten
                 update: function () {
                   Object.keys(this).forEach(name => {
                

                Hier beiss ich mir an deinem Beispiel gerade die Zähne aus.

                Das war zu erwarten. Hier habe ich zu viel Abstraktion auf einmal auf Dich geworfen. Mal sehen, ob wir zumindest einen Teil verstanden bekommen.

                Die Methode "update" ruft eine Funktion ohne Namen auf "update: function()".

                Jein. Das Objekt hat eine Methode update, die ihren Code nach dem Doppelpunkt notiert bekommt. Vergleiche diese Schreibweisen:

                function tuWas (param) {
                  console.log(param);
                }
                
                const tuWasAnderes = function (param) {
                  console.log(param);
                }; // mit abschließendem Semikolon
                
                const myTuWas = tuWas;
                const myTuWasWert = tuWas(); // undefined
                

                Das erste Beispiel sollte klar sein: Es wird eine Funktion namentlich definiert, weil da „einfach so“ ein Funktionsliteral steht.

                Das zweite Beispiel definiert eine Konstante namens tuWasAnderes, die einen Wert zugewiesen bekommt. Dieser Wert ist als Funktionsliteral geschrieben, welches eine anonyme Funktion darstellt. Damit wird die Konstante tuWasAnderes zu einer Funktion.

                Das dritte Beispiel ist ebenso eine Wertzuweisung wie das zweite Beispiel, nur dass hier eine bereits definierte Funktion als Wert zugewiesen wird. Ab dem Moment kann man tuwas() und myTuWas() synonym verwenden.

                Das vierte Beispiel soll den Unterschied zwischen der Zuweisung einer Funktion selbst und der Zuweisung ihres Rückgabewerts zeigen. Wenn man myTuWas aufruft, gibt sie keinen Wert zurück, weshalb myTuWasWert ein undefined enthält.

                Im Objektliteral, in dem update: function () { ... } steht, steht im Grunde eine Wertzuweisung, wie im zweiten Beispiel mit der Konstante tuWasAnderes.

                Dann folgt der Konstrukt dieser Funktion worin ein Objekt aufgerufen wird, das die Methode ForEach() besitzt -> "Object.keys(this).forEach()". Sollte ich das soweit richtig interpretiert haben, so habe ich es leider noch nicht entschlüsseln können, was "Object.keys() genau macht.

                Hier gehen wir ein bisschen auf die Meta-Ebene der Programmierung. Object.keys() liefert die Namen aller Eigenschaften und Methoden eines Objektes zurück, also alles das, was bei der Schreibweise als Objektliteral vor den Doppelpunkten gestanden hat. Diese Liste liefert es als Liste in Form des Datentyps Array zurück. Ein Array ist natürlich auch wieder ein Objekt, weil alles in JavaScript ein Objekt ist. Um nun an die Elemente darin einzeln heranzukommen, kann man entweder eine Schleife schreiben (z.B. eine for-Schleife), oder man verwendet die Array-Methode forEach dafür, die als Parameter eine weitere Funktion haben will, die dann ihrerseits das Element zur Verarbeitung bekommt.

                Die Pfeilfunktion ist eine anonyme Funktion, die einen Parameter in der Variable name aufnimmt. In unserem Beispiel bekommt sie als Wert den Namen einer Eigenschaft oder Methode unseres PVA-Objekts. Diese Eigenschaft wird darauf untersucht, ob ihre loadData-Eigenschaft vom Typ function ist. Wenn es keine solche gibt, ist der Datentyp dann undefined. Handelt es sich aber um den Datentyp function, ist es eine Methode und wird dann sofort ausgeführt.

                An dieser Stelle verwende ich deshalb eine Pfeilfunktion, weil sie die Bedeutung von this nicht ändert.

                Ich erahne nur, dass hier möglicherweise mit "this" (was geschätzt wohl das Objekt PVA repräsentiert) das gesamte PVA-Objekt nach Methoden mit der Bezeichnung "loadData" durchsucht wird.

                Das schlüsselwort this verweist immer auf das Objekt, in dessen Kontext eine Methode aufgerufen wurde. In diesem Fall ist das in der Tat das PVA-Objekt. Aber nicht dessen Methoden werden auf loadData geprüft, sondern ob dessen Eigenschaften eine solche Methode besitzen, weil sie selbst Objekte mit Methoden sind.

                Wenn eine solche Methode gefunden wurde, wird diese dann mittels this[name].loadData() aufgerufen und somit ausgeführt. Aber wirklich sicher bin ich mir hier nicht.

                Das ist genau richtig. Wenn ich den Namen einer Eigenschaft in einer Variable stehen habe, dann kann ich nicht die Punkt-Schreibweise verwenden, sondern muss die eckigen Klammern um die Variable schreiben:

                const name = "Status";
                
                PVA.Batterie.Status = 1;
                PVA.Batterie[name] = 2;
                PVA.Batterie.name = 3;
                
                console.log(PVA.Batterie.Stats); // 2
                

                Die letzte Wertzuweisung erzeugt in obigem Beispiel eine neue Eigenschaft für das PVA-Objekt, die den Namen „name“ trägt und den Wert 3 zugewiesen bekommt.

                Hilft Dir das weiter?

                Liebe Grüße

                Felix Riesterer

                1. @@Felix Riesterer

                    loadData () {
                      this.Status = getState('sun2000.' + this.index + '.collected.SOC').val;
                      this.LadungEntladung = getState('sun2000.' + this.index + '.collected.chargeDischargePower').val;
                    }
                  

                  Yikes! Warum die unübersichtliche Schreibweise? Template-Literale machen das lesbarer:

                    loadData () {
                      this.Status = getState(`sun2000.${this.index}.collected.SOC`).val;
                      this.LadungEntladung = getState(`sun2000.${this.index}.collected.chargeDischargePower`).val;
                    }
                  

                  Kwakoni Yiquan

                  --
                  Ad astra per aspera
                  1. Hallo,

                    Yikes! Warum die unübersichtliche Schreibweise?

                    weil die Beurteilung, was übersichtlich ist und was nicht, eine subjektive ist, und stark von Gewöhnung beeinflusst wird...

                    Gruß
                    Kalk

                  2. Hallo,

                    Yikes! Warum die unübersichtliche Schreibweise? Template-Literale machen das lesbarer:

                    ich bin da ganz bei Tabellenkalk: Das ist ein sehr subjektiver Eindruck. Für mich sind die Template-Literale, die du propagierst, nicht übersichtlicher, aber YMMV.

                    Das ist eine Sache der Gewohnheit.

                    Einen schönen Tag noch
                     Martin

                    --
                    Wichtige Erkenntnis für Comiczeichner:
                    Eine Sprechblase ist nicht unbedingt ein Fall für den Urologen.
  2. Dieser Beitrag wurde gelöscht: Beitrag ist Spam.