2 Min

Ein gängiges Muster in der objektorientierten Programmierung ist die Definition einer benutzerdefinierten equals-Methode innerhalb der Klasse. Du kannst sie implementieren, um Instanzen zu vergleichen, indem du ihre internen Werte prüfst:

class Container {
  #name;

  constructor(a) {
    this.#name = a;
  }

  valueOf() {
    return this.#name;
  }

  equals(other) {
    return other instanceof Container && this.valueOf() === other.valueOf();
  }
}

main();

function main() {
  const a = new Container("XYZ");
  const b = new Container("XYZ");
  test(1, a < b, false); // 1. BESTANDEN
  test(2, a <= b, true); // 2. BESTANDEN
  test(3, a === b, true); // 3. FEHLER (a und b sind unterschiedliche Instanzen)
  test(4, a.equals(b), true); // 4. PASS (mit der benutzerdefinierten Methode „equals“)
  test(5, a.valueOf() === b.valueOf(), true); // 5. PASS
}

function test(n, a, x) {
  if (a === x) console.log(n + ". PASS");
  else console.log(n + ". FAIL: expected " + x);
}

Auf diese Weise kannst du zwei Instanzen mit „a.equals(b)“ vergleichen, um anhand der internen Werte auf Gleichheit zu prüfen.


Wenn du in JavaScript zwei Klasseninstanzen mit „===“ vergleichst, wird auf Referenzgleichheit geprüft, d. h. es wird verglichen, ob beide Variablen auf genau dasselbe Objekt im Speicher verweisen. Aus diesem Grund schlägt „a === b“ in Test 3 fehl: „a“ und „b“ sind zwei verschiedene Instanzen der Klasse „Container“, auch wenn sie möglicherweise denselben internen Wert („XYZ“) enthalten. Die Methode valueOf wird bei solchen Vergleichen nicht automatisch aufgerufen.

Für Test 4 ruft der Ausdruck +a === +b valueOf() auf, da der unäre +-Operator versucht, das Objekt in eine primitive Zahl umzuwandeln (mit valueOf). Da valueOf jedoch eine Zeichenfolge ("XYZ") zurückgibt, versucht JavaScript, diese Zeichenfolge in eine Zahl umzuwandeln. Da "XYZ" nicht in eine Zahl umgewandelt werden kann, ergibt sich NaN. Daher gibt der Vergleich NaN === NaN false zurück, was zu einem fehlgeschlagenen Test führt.

Weitere Möglichkeiten zum Vergleichen der Werte innerhalb der Klasseninstanzen:

Verlassen Sie sich bei Vergleichen explizit auf valueOf

Du kannst bei Vergleichen immer die Methode .valueOf() verwenden, um den internen Wert explizit zu extrahieren:

test(4, a.valueOf() === b.valueOf(), true); // Dies funktioniert, da Sie die Zeichenfolgenwerte direkt vergleichen.

Dieser Ansatz funktioniert, ist aber weniger elegant als die Definition einer equals-Methode, weil du immer daran denken musst, valueOf() aufzurufen.

Bedenke:

  • Test 3 (a === b) schlägt fehl, da === auf Referenzgleichheit prüft, nicht auf Wertegleichheit.
  • Test 4 (+a === +b) schlägt fehl, da +a versucht, die Zeichenfolge "XYZ" in eine Zahl umzuwandeln, was zu NaN führt.
  • Die einfachste Lösung besteht darin, Ihrer Klasse eine equals-Methode zum Wertevergleich hinzuzufügen oder explizit a.valueOf() === b.valueOf() für Gleichheitsprüfungen zu verwenden.

Bonus: Verbesserung der Lesbarkeit durch Verwendung von Symbol.toPrimitive

Du kannst Symbol.toPrimitive auch definieren, um zu steuern, wie das Objekt bei Bedarf in ein Primitiv (Zeichenfolge oder Zahl) umgewandelt wird:

class Container {
  #name;

  constructor(a) {
    this.#name = a;
  }

  valueOf() {
    return this.#name;
  }

  [Symbol.toPrimitive](hint) {
    if (hint === "string") {
      return this.#name; // Bei Umwandlung in Zeichenfolge
    }
    return this.#name; // Umwandlung in Standard (Zahl oder Zeichenfolge)
  }
}

main();

function main() {
  const a = new Container("XYZ");
  const b = new Container("XYZ");
  test(1, a < b, false); // 1. PASS
  test(2, a <= b, true); // 2. BESTANDEN
  test(3, a === b, true); // 3. FEHLER (a und b beginnen immer noch unterschiedliche Instanzen)
  test(4, a + '' === b + '', true); // 4. BESTANDEN (String-Koerzierung funktioniert dank Symbol.toPrimitive)
  test(5, a.valueOf() === b.valueOf(), true); // 5. BESTANDEN
}

Dadurch kannst Du steuern, wie sich das Objekt verhält, wenn es in eine Primitive umgewandelt wird (wie mit +a oder a + "" für die String-Verkettung).

Updated: