ebody: Javascript Reference Types Beispiel und Frage

problematische Seite

Hallo,

warum behält elementNum den ursprünglichen Wert? Es wird zwar ein Value Type gespeichert, aber dieser wird aus this.arrAllDatasets (Reference Type) genommen und wenn sich der Wert von einem Reference Type ändert, wird er überall geändert. So habe ich es zumindest verstanden.

/*
 * Warum behält elementNum den ursprünglichen Wert?
 */

class Datasets{
  constructor(arrData) {
    // Reference Type
    this.arrAllDatasets = arrData;
    // Reference Type
    this.datasets = this.arrAllDatasets;
    // Value Type von einem Reference Type
    this.elementNum = this.arrAllDatasets.length;
  }
  
}

let myMovieDb = new Datasets(['Prometheus','Free Guy','Shutter Island','The Avengers']);

// Reference Type
console.log('arrAllDatasets 1: ', myMovieDb.arrAllDatasets);    // arrAllDatasets 1: ['Prometheus', 'Free Guy', 'Shutter Island', 'The Avengers']

// Reference Type Wert ändern
myMovieDb.datasets.splice(2, 2);

// Value Type von einem Reference Type
console.log('elementNum: ', myMovieDb.elementNum);              // elementNum:  4

// Reference Type
console.log('datasets: ', myMovieDb.datasets);                  // datasets:  (2) ['Prometheus', 'Free Guy'

// Reference Type
console.log('arrAllDatasets 2: ', myMovieDb.arrAllDatasets);    // arrAllDatasets 2:  (2) ['Prometheus', 'Free Guy']

Hintergrund der Frage ist, dass ich das Array nur einmal an den Constructor übergeben möchte und dann das Array einmal mit allen ursprünglichen Daten speichern möchte und einmal mit geänderten Daten. Wenn z.B. eine Methode aufgerufen wird, um Daten in das Array hinzuzufügen oder zu entfernen.

Das habe ich bisher nur hinbekommen. wenn ich das Array in der Klasse in einer get Methode direkt festlege. Aber das Array soll an die Klasse übergeben werden. Ich habe dafür eine set Methode erstellt, aber dann hat es schon nicht mehr funktioniert.

Oder wäre es falsch, schlechter Programmierstil, wenn man in einem Objekt 2 verschiedene Zustände vom Array speichern möchte und es wäre besser einfach 2 Objekte zu erstellen? In dem einen speicher die ursprünglichen Array Daten, in dem anderen die angepassten Daten?

Gruß ebody

  1. problematische Seite

    Tach!

    warum behält elementNum den ursprünglichen Wert?

    elementNum wird einmalig im Konstuktor zugewiesen. Der Wert ist ein primitiver Typ, mit der Länge des Arrays zum damaligen Zeitpunkt belegt.

    Wenn du die aktuelle länge haben möchtest, musst du this.arrAllDatasets.length befragen und nicht eine Kopie eines ehemaligen Wertes. Eine get-Property kann den Wert liefern, oder eine Methode.

    Hintergrund der Frage ist, dass ich das Array nur einmal an den Constructor übergeben möchte und dann das Array einmal mit allen ursprünglichen Daten speichern möchte und einmal mit geänderten Daten. Wenn z.B. eine Methode aufgerufen wird, um Daten in das Array hinzuzufügen oder zu entfernen.

    Dazu musst du eine Kopie des Arrays erzeugen. Eine einfache Zuweisung gibt nur die Referenz weiter. Beide Eigenschaften zeigen damit auf dasselbe Array. Eine einfache Kopie kann mit .slice() (ohne Parameter) angelegt werden. Damit wird das Array kopiert. Da du Strings als Inhalte hast, werden diese kopiert. Bei Objekten würden nur die Referenzen kopiert werden.

    dedlfix.

  2. problematische Seite

    Hallo ebody,

    warum elementNum sich nicht ändert, hat Dedlfix erklärt. Ich möchte nur ergänzen: elementNum ist ein blöder Name. "Anzahl" ist eher Count, nicht "Number". Nenn das Property elementCount.

    Aber brauchst Du dieses Property überhaupt? Das Array ist als Eigenschaft verfügbar und sein length Property lässt sich jederzeit abfragen.

    Die slice-Methode ist eine Möglichkeit, ein Array zu kopieren. Das neue Array ist genau so lang und enthält die gleichen Werte wie die Kopierquelle. Die Alternative in neueren Browern ist der Spread-Operator:

    let original = [1, 2, 3];
    let kopie = [... original];
    

    Das ist nicht ganz das gleiche, sofern Arrayindizes übersprungen wurden, aber in einem normalen Array mit dicht liegenden Indizes schon.

    Diesen Kopiervorgang muss man aber genauer betrachten. Wenn das Array Werte eines primitiven Typs enthält, werden die Werte echt gedoppelt. Mit Ausnahme von String. Strings werden bei einer Zuweisung nicht gedoppelt - ein String besteht aus einer String-Information (wo im Speicher, wie lang) und der eigentlichen Zeichenkette. Gedoppelt wird nur die String-Information, die Zeichenkette teilen sich die beiden. Das ist unproblematisch, weil Strings in JavaScript unveränderlich sind. Ich weise auch nur deshalb darauf hin, weil man meinen könnte, das Doppeln eines Arrays mit 100 Einträgen aus String der Länge 10000 wäre eine teure Operation für Laufzeit und Speicher. Ist sie nicht, es ist genauso teuer wie das Doppeln jedes anderen Arrays.

    Enthält das Array ein Objekt, wird nur die Objektreferenz gedoppelt. D.h. die Kopie und das Original zeigen danach auf das gleiche Objekt. Wenn man das nicht will, braucht man einen deep clone Algorithmus, der auch die referenzierten Objekte doppelt - und zwar rekursiv, denn die können ja auch wieder auf Objekte zeigen. Das ist nicht trivial - solche Verweisketten können geschlossen sein und das würde eine Endlosrekursion hervorrufen.

    Von der Architektur her solltest du aber das datasets-Array nicht direkt manipulieren. Wenn die Datasets-Klasse dieses Array speichert, dann sollte es ihr auch gehören und von außen nicht manipuliert werden. Schreibe Methoden, die diese Manipulationen durchführen.

    In neueren Browsern kannst Du auch dafür sorgen, dass Eigenschaften eines Objekts privat sind:

    class Dataset {
       #originalValues;
       #currentValues;
    
       constructor(values) {
          this.#originalValues = values;
          this.#currentValues = [...values];
       }
       get originalCount() { return this.#originalValues.length; }
       get count() { return this.#currentValues.length; }
    }
    
    let d = new Dataset([1,2,3]);
    d.#currentValues.splice(1,1);   // ERROR
    

    Ob es richtig oder falsch ist, sowohl Original als auch Modifikation zu speichern, hängt von dem Zweck der Klasse ab. Gibt es Operationen, die beide Versionen benötigen? Gibt es mehrere Datasets, alle mit ihren eigenen Originalen?

    Im System.Data Namespace von Microsoft.net gibt es die Komponente ADO.NET (Active Data Objects). Die hat eine DataTable Klasse, die deinem Dataset entspricht. Eine DataTable ist ein Array aus DataRows. In jeder DataRow wird gespeichert, ob sie gelöscht, modifiziert oder neu hinzugefügt wurde. Eine Original-Row, die modifiziert wird, existiert nachher zweimal: im Original und die modifizierte Fassung. Das führt dann dazu, dass die ADO.NET Komponente ("Active Data Objects") aus einer modifizierten Datatable automagisch die SQL Statements generieren kann, die nötig sind, um die Änderungen in die Datenbank zurückzuschreiben. Willst Du sowas tun?

    Rolf

    --
    sumpsi - posui - obstruxi