Zum Inhalt
wannacode

static: was alle Objekte teilen

Kurz gesagt: Ein statisches Feld oder eine statische Methode gehört zur Klasse, nicht zum Objekt: Alle Objekte sehen einen gemeinsamen Wert, zum Beispiel einen Zähler der erzeugten Objekte.

Problem

Jedes Produkt ist Nr. 1

Der Shop will, dass jedes neue Produkt die nächste Nummer bekommt: 1, 2, 3… Du legst einen Zähler #nextId in die Klasse. Der Konstruktor gibt dem Produkt die aktuelle Nummer und erhöht den Zähler:

class Product {
#nextId = 1;
#id;
#name;
constructor(name) {
this.#name = name;
this.#id = this.#nextId;
this.#nextId++;
}
toString() {
return `Nr. ${this.#id} ${this.#name}`;
}
}
const bread = new Product("Brot");
const milk = new Product("Milch");
const coffee = new Product("Kaffee");
console.log(String(bread));
console.log(String(milk));
console.log(String(coffee));
Ausgabe
Nr. 1 Brot
Nr. 1 Milch
Nr. 1 Kaffee

Alle sind Nr. 1. Der Grund ist, dass #nextId ein Feld des Objekts ist, genau wie der Name. Jedes neue Produkt bekommt seinen eigenen Zähler, der bei 1 beginnt. Der Zähler des Brotes kommt nie bei der Milch an.

Die Idee

Ein Feld der Klasse, nicht des Objekts

Der Zähler gehört nicht zu einem Produkt. Alle Produkte teilen ihn sich. Füge static hinzu:

static #nextId = 1;

Ein static-Feld ist ein Feld der Klasse. Es gibt eines, egal wie viele Objekte du erzeugst, und alle Objekte teilen es sich. Im Konstruktor erreichst du es über den Namen der Klasse: Product.#nextId.

shop.js
class Product {
static #nextId = 1;
#id;
#name;
constructor(name) {
this.#name = name;
this.#id = Product.#nextId;
Product.#nextId++;
}
toString() {
return `Nr. ${this.#id} ${this.#name}`;
}
}
const bread = new Product("Brot");
const milk = new Product("Milch");
const coffee = new Product("Kaffee");
console.log(String(bread));
console.log(String(milk));
console.log(String(coffee));
Ausgabe
Nr. 1 Brot
Nr. 2 Milch
Nr. 3 Kaffee

Das Brot nimmt die 1 und macht den Zähler zu 2. Die Milch sieht denselben Zähler, nimmt die 2 und macht ihn zu 3.

static #nextId = 1;
constructor(name) {
this.#name = name;
this.#id = Product.#nextId;
Product.#nextId++;
}
const bread = new Product("Brot");
const milk = new Product("Milch");
const coffee = new Product("Kaffee");
Schritt für Schritt, in Worten
  1. #nextId ist static: ein Feld in der Klasse, nicht eines in jedem Objekt.
  2. new Product("Brot"): ein neues Objekt. Der Konstruktor speichert den Namen.
  3. this.#id = Product.#nextId: Das Objekt nimmt die aktuelle Nummer, 1.
  4. Product.#nextId++ ändert den gemeinsamen Zähler: Jetzt ist er 2.
  5. Milch: derselbe Zähler. Es nimmt die 2 und macht ihn zu 3.
  6. Kaffee: Es nimmt die 3 und macht ihn zu 4.
  7. Es gibt einen Zähler für alle, deshalb sind die Nummern der Reihe nach.

Ein static-Feld ist über ein Objekt nicht sichtbar. Von außen erreichst du es über den Namen der Klasse (Order.count):

class Order {
static count = 0;
}
const order = new Order();
console.log(Order.count, order.count);
Ausgabe
0 undefined

Probier es aus

Gemeinsam und eigen

Was gibt das Programm aus?

class Order {
static count = 0;
items = 0;
constructor() {
Order.count++;
this.items++;
}
}
const a = new Order();
const b = new Order();
const c = new Order();
console.log(Order.count, c.items);
Antwort

Die Idee

Eine statische Methode: Arbeit ohne Objekt

Dasselbe gilt für Methoden. Eine static-Methode ist eine Methode der Klasse. Du rufst sie über den Namen der Klasse auf, ohne new:

class Calculator {
static add(a, b) {
return a + b;
}
static average(values) {
let sum = 0;
for (const v of values) {
sum += v;
}
return sum / values.length;
}
}
console.log(Calculator.add(10, 20));
const grades = [5, 6, 4, 6];
console.log(Calculator.average(grades));
console.log(Math.max(3, 8));
Ausgabe
30
5.25
8

Calculator.add braucht kein Objekt: Alles, was sie braucht, kommt als Parameter. Die Methoden von Math sind genauso: Math.max, Math.sqrt. Aus Math erzeugst du keine Objekte, und new Math() hält das Programm an:

Fehler
TypeError: Math is not a constructor

JavaScript hat kein main: Das Programm sind die Zeilen der Datei, von oben nach unten. Deshalb brauchten die Funktionen aus dem Modul „Funktionen“ keine Klasse.

Genauer, für Neugierige

Umgekehrt geht es auch: In einer static-Methode kannst du ein Objekt erzeugen und seine Methoden aufrufen.

Probier es aus

Finde den Fehler

tea.label() hält das Programm an. In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

Konstanten

Die MwSt. ist für alle Produkte 20%, und sie ändert sich nicht, während das Programm läuft. So ein Wert ist eine Konstante:

const VAT_RATE = 0.20;
class Prices {
static withVat(price) {
return price * (1 + VAT_RATE);
}
}
console.log(Prices.withVat(50));
console.log(`MwSt.: ${VAT_RATE * 100}%`);
Ausgabe
60
MwSt.: 20%

const bedeutet, dass die Variable einmal gesetzt wird und sich nicht ändern lässt. Du setzt sie außerhalb der Klasse, oben in die Datei. Die Namen von Konstanten schreibt man mit Großbuchstaben und Unterstrichen: VAT_RATE.

Wenn jemand versucht, sie zu ändern,

VAT_RATE = 0.25;

hält das Programm an:

Fehler
TypeError: Assignment to constant variable.

Du gewinnst doppelt: Die Zahl 0.20 steht an einer Stelle mit einem klaren Namen, und niemand kann sie aus Versehen ändern.

Genauer, für Neugierige

Ein static-Feld in der Klasse ist keine Konstante: Es lässt sich von außen ändern.

class Prices {
static VAT_RATE = 0.20;
}
Prices.VAT_RATE = 0.25;
console.log(Prices.VAT_RATE);
Ausgabe
0.25

Manchmal ist der Wert für jedes Objekt anders, darf sich aber nach dem Erzeugen des Objekts nicht ändern. So ist die Nummer des Produkts. Mach sie zu einem privaten Feld mit einem Getter und ohne Setter (die Lektion „Kapselung“):

class Product {
static #nextId = 1;
#id;
#name;
constructor(name) {
this.#name = name;
this.#id = Product.#nextId;
Product.#nextId++;
}
get id() {
return this.#id;
}
}

coffee.id liest die Nummer, und coffee.id = 5 ändert sie nicht: Das Feld #id ist privat und hat nur einen Getter.

Probier es aus

Was fehlt?

Ein Jahr hat 12 Monate, und das wird sich nicht ändern. Was steht in der Lücke?
 MONTHS = 12;

Die Idee

Wann static und wann nicht

Frag dich: „Gehört das zu einem konkreten Objekt?“

  • Ja (der Kontostand des Kontos, der Name des Produkts, das Abheben von einem Konto): ein Feld oder eine Methode des Objekts, ohne static.
  • Nein (die Anzahl aller Konten, die MwSt., die Summe zweier Zahlen): static.

Ein häufiger Fehler ist, alles static zu machen, damit du keine Objekte erzeugen musst. Dann teilen sich alle „Objekte“ dieselben Daten, und du bist wieder beim Problem mit den parallelen Arrays aus der ersten Lektion.

Übung

Was alle Objekte teilen

Löse die Aufgaben in deinem Editor.

1. Bestellungen. Eine Klasse Order mit Kunde und Betrag. Jede neue Bestellung bekommt die nächste Nummer aus einem statischen Zähler. Ein statischer Getter count sagt, wie viele Bestellungen erzeugt wurden.

Das Programm
const orders = [
new Order("Maria", 45.5),
new Order("Peter", 120),
new Order("Elena", 18.9),
];
for (const o of orders) {
console.log(String(o));
}
console.log(`Bestellungen: ${Order.count}`);
Ausgabe
Bestellung Nr. 1: Maria, 45.5 €
Bestellung Nr. 2: Peter, 120 €
Bestellung Nr. 3: Elena, 18.9 €
Bestellungen: 3

2. Taschenrechner. Eine Klasse Calculator mit statischen Methoden zum Addieren, Subtrahieren und Multiplizieren ganzer Zahlen und zum Dividieren von Dezimalzahlen.

Das Programm
console.log(Calculator.add(10, 20));
console.log(Calculator.subtract(10, 15));
console.log(Calculator.multiply(6, 7));
console.log(Calculator.divide(5, 2));
Ausgabe
30
-5
42
2.5

3. Wechselstube. Eine Klasse Converter mit Konstanten für den Kurs: 1 € = 1,08 US-Dollar und 1 € = 0,85 britische Pfund (die Kurse sind Beispiele). Statische Methoden rechnen einen Betrag in Euro in Dollar und in Pfund um. Für 20 €:

Ausgabe
20 EUR = 21.6 USD
20 EUR = 17 GBP

Weitere Aufgaben

  • Nummerierte Konten. Gib dem Account aus der Lektion „Kapselung“ eine Kontonummer aus einem statischen Zähler und einen Getter ohne Setter für die Nummer.
  • Umrechnen in beide Richtungen. Gib Converter Methoden, die von Dollar und von Pfund in Euro umrechnen. Rechne 20 € in Dollar um und zurück in Euro. Bekommst du genau 20?
  • Ein Limit. Der Shop nimmt höchstens 3 Produkte in einem Warenkorb an. Füge dafür eine Konstante hinzu und lass die Methode zum Hinzufügen das vierte ablehnen.

Kurz gesagt

Das Modul in kurz

  • Eine Klasse ist dein eigener Typ, und ein Objekt ist ein konkretes Exemplar davon, erzeugt mit new. Die Variable zeigt auf das Objekt. Klassen und Objekte
  • constructor sorgt dafür, dass das Objekt vollständig entsteht. this ist das aktuelle Objekt. Konstruktoren und this
  • Instanzmethoden arbeiten mit den Feldern des Objekts. toString() sagt, wie es als Text aussieht. Instanzmethoden
  • #-Felder sind privat. Von außen kommst du nur über get und set an sie, die prüfen. Kapselung
  • static ist das, was die Klasse teilt: Zähler und Hilfsmethoden. Konstanten sind const.

Daten und Regeln an einer Stelle, die niemand umgehen kann: Das ist objektorientierte Programmierung. Im nächsten Modul fangen Klassen an, Code miteinander zu teilen.

Test

Teste dich

1. Du erzeugst 5 Objekte aus einer Klasse mit dem Feld static count = 0. Wie viele Kopien von count liegen im Speicher?
2. Was kann eine statische Methode NICHT?
3. Order hat static count = 0. Was liefert order.count für ein Objekt order?
4. Was passiert bei VAT_RATE = 0.25;, wenn VAT_RATE mit const deklariert ist?
5. Was ist ein guter Kandidat für eine statische Methode?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·