Start · Sprachen · JavaScript · Referenz · Object

Object

Klasse

Der Typ <code>Object</code> repräsentiert einen der Datentypen von JavaScript und dient zur Speicherung von Schlüssel-Wert-Sammlungen und komplexeren Entitäten.

seit JavaScript ES1 Kategorie: core

Signatur

new Object() / new Object(value) / Object() / Object(value)

Beschreibung

Nahezu alle Objekte in JavaScript sind Instanzen von Object; ein typisches Objekt erbt Eigenschaften (einschließlich Methoden) von Object.prototype, wobei diese Eigenschaften überschattet (also überschrieben) werden können. Die einzigen Objekte, die nicht von Object.prototype erben, sind solche mit null-Prototyp oder solche, die von anderen Objekten mit null-Prototyp abstammen.

Änderungen am Objekt Object.prototype sind über die Prototype-Kette für alle Objekte sichtbar, sofern die betroffenen Eigenschaften und Methoden nicht weiter unten in der Prototype-Kette überschrieben werden. Dies stellt einen sehr mächtigen, aber potenziell gefährlichen Mechanismus zum Überschreiben oder Erweitern von Objektverhalten bereit. Zur Erhöhung der Sicherheit ist Object.prototype das einzige Objekt in der JavaScript-Kernsprache mit unveränderlichem Prototyp — der Prototyp von Object.prototype ist stets null und nicht änderbar.

Eigenschaften des Object-Prototyps

Man sollte es vermeiden, Methoden von Object.prototype direkt auf einer Instanz aufzurufen, insbesondere solche, die nicht als polymorph gedacht sind (d. h. nur ihr ursprüngliches Verhalten sinnvoll ist und kein abgeleitetes Objekt sie sinnvoll überschreiben könnte). Alle von Object.prototype abstammenden Objekte können eine eigene Eigenschaft mit demselben Namen definieren, aber mit völlig anderer Semantik als erwartet. Zudem werden diese Eigenschaften nicht von Objekten mit null-Prototyp geerbt. Alle modernen JavaScript-Werkzeuge zum Arbeiten mit Objekten sind statisch. Genauer:

  • valueOf(), toString() und toLocaleString() existieren, um polymorph zu sein, und man sollte erwarten, dass das Objekt eine eigene Implementierung mit sinnvollem Verhalten definiert, sodass sie als Instanzmethoden aufgerufen werden können. Allerdings werden valueOf() und toString() üblicherweise implizit durch Typkonvertierung aufgerufen, sodass man sie im eigenen Code nicht selbst aufrufen muss.
  • __defineGetter__(), __defineSetter__(), __lookupGetter__() und __lookupSetter__() sind veraltet und sollten nicht verwendet werden. Verwenden Sie stattdessen die statischen Alternativen Object.defineProperty() und Object.getOwnPropertyDescriptor().
  • Die Eigenschaft __proto__ ist veraltet und sollte nicht verwendet werden. Die Alternativen Object.getPrototypeOf() und Object.setPrototypeOf() sind statische Methoden.
  • Die Methoden propertyIsEnumerable() und hasOwnProperty() können jeweils durch die statischen Methoden Object.getOwnPropertyDescriptor() bzw. Object.hasOwn() ersetzt werden.
  • Die Methode isPrototypeOf() kann meist durch instanceof ersetzt werden, sofern man die prototype-Eigenschaft eines Konstruktors prüft.

Wenn keine semantisch äquivalente statische Methode existiert oder man wirklich die Object.prototype-Methode nutzen möchte, sollte man die Object.prototype-Methode direkt per call() auf dem Zielobjekt aufrufen, um zu verhindern, dass eine überschreibende Eigenschaft des Objekts unerwartete Ergebnisse liefert.

Eine Eigenschaft aus einem Objekt löschen

Ein Objekt selbst besitzt keine Methode zum Löschen eigener Eigenschaften (wie etwa Map.prototype.delete()). Um dies zu tun, muss der delete-Operator verwendet werden.

null-prototype-Objekte

Fast alle Objekte in JavaScript erben letztendlich von Object.prototype. Man kann jedoch Objekte mit null-Prototyp erzeugen, indem man Object.create(null) oder die Object-Initializer-Syntax mit __proto__: null verwendet (Hinweis: Der Schlüssel __proto__ in Objektliteralen unterscheidet sich von der veralteten Eigenschaft Object.prototype.__proto__). Man kann außerdem den Prototyp eines vorhandenen Objekts auf null setzen, indem man Object.setPrototypeOf(obj, null) aufruft.

Ein Objekt mit null-Prototyp kann sich unerwartet verhalten, da es keine Objektmethoden von Object.prototype erbt. Dies gilt besonders beim Debuggen, da übliche Hilfsfunktionen zur Konvertierung/Erkennung von Objekt-Eigenschaften Fehler erzeugen oder Informationen verlieren können (insbesondere bei stillen Fehler-Traps, die Fehler ignorieren).

Beispielsweise macht das Fehlen von Object.prototype.toString() das Debuggen oft undurchführbar:

Andere Methoden schlagen ebenfalls fehl.

Wir können die Methode toString dem null-prototype-Objekt wieder hinzufügen, indem wir sie ihm zuweisen:

Im Gegensatz zu normalen Objekten, bei denen toString() auf dem Prototyp des Objekts liegt, ist die Methode toString() hier eine eigene Eigenschaft von nullProtoObj. Das liegt daran, dass nullProtoObj keinen (null) Prototyp hat.

Man kann ein null-prototype-Objekt auch mit Object.setPrototypeOf(nullProtoObj, Object.prototype) zu einem gewöhnlichen Objekt zurückverwandeln.

In der Praxis werden Objekte mit null-Prototyp meist als billiger Ersatz für Maps eingesetzt. Das Vorhandensein von Object.prototype-Eigenschaften verursacht einige Bugs:

Ein null-prototype-Objekt beseitigt diese Gefahr, ohne den Funktionen hasPerson und getAge zu viel Komplexität hinzuzufügen:

In diesem Fall sollte das Hinzufügen von Methoden mit Vorsicht erfolgen, da sie mit anderen als Daten gespeicherten Schlüssel-Wert-Paaren verwechselt werden können.

Wenn das eigene Objekt nicht von Object.prototype erbt, verhindert dies zudem Prototype-Pollution-Angriffe. Fügt ein bösartiges Skript eine Eigenschaft zu Object.prototype hinzu, ist diese auf jedem Objekt im Programm zugänglich, außer auf Objekten mit null-Prototyp.

JavaScript besitzt auch eingebaute APIs, die null-prototype-Objekte erzeugen, insbesondere solche, die Objekte als Ad-hoc-Schlüssel-Wert-Sammlungen nutzen. Zum Beispiel:

  • Der Rückgabewert von Object.groupBy()
  • Die Eigenschaften groups und indices.groups des Ergebnisses von RegExp.prototype.exec()
  • Array.prototype[Symbol.unscopables] (alle [Symbol.unscopables]-Objekte sollten null-Prototyp haben)
  • import.meta
  • Modul-Namespace-Objekte, erhalten über import * as ns from "module"; oder import()

Der Begriff „null-prototype-Objekt" schließt oft auch jedes Objekt ohne Object.prototype in seiner Prototype-Kette ein. Solche Objekte können mit extends null bei Verwendung von Klassen erzeugt werden.

Object Coercion

Viele eingebaute Operationen, die Objekte erwarten, wandeln ihre Argumente zunächst in Objekte um. Die Operation lässt sich wie folgt zusammenfassen:

  • Objekte werden unverändert zurückgegeben.
  • undefined und null werfen einen TypeError.
  • Number-, String-, Boolean-, Symbol-, BigInt-Primitives werden in ihre entsprechenden Object-Wrapper eingepackt.

In JavaScript gibt es zwei Wege, nahezu denselben Effekt zu erzielen.

  • Object.prototype.valueOf(): Object.prototype.valueOf.call(x) führt genau die oben erklärten Schritte der Object Coercion aus, um x umzuwandeln.
  • Die Funktion Object(): Object(x) verwendet denselben Algorithmus zur Umwandlung von x, außer dass undefined und null keinen TypeError werfen, sondern ein einfaches Objekt zurückgeben.

Stellen, die Object Coercion nutzen, sind u. a.:

  • Der object-Parameter von for...in-Schleifen.
  • Der this-Wert von Array-Methoden.
  • Parameter von Object-Methoden wie Object.keys().
  • Auto-Boxing, wenn auf einem primitiven Wert eine Eigenschaft zugegriffen wird, da Primitives keine Eigenschaften besitzen.
  • Der this-Wert beim Aufruf einer Nicht-Strict-Funktion. Primitives werden geboxt, während null und undefined durch das globale Objekt ersetzt werden.

Im Gegensatz zur Konvertierung in Primitives ist der Prozess der Object Coercion an sich in keiner Weise beobachtbar, da er keinen benutzerdefinierten Code wie toString- oder valueOf-Methoden aufruft.

Parameter

Name Typ Default Beschreibung
$value any undefined Optionaler Wert, der in ein Objekt umgewandelt werden soll. Bei null oder undefined wird ein leeres Objekt zurückgegeben; bei primitiven Werten das entsprechende Wrapper-Objekt.

Rückgabewert

Typ
Object
Beschreibung
Ein neues Objekt. Wird ein Objekt übergeben, wird dieses unverändert zurückgegeben. Wird ein Primitiv übergeben, entsteht das entsprechende Wrapper-Objekt (z. B. Number, String, Boolean).

Beispiele

Leere Objekte erzeugen

const o1 = new Object();
const o2 = new Object(undefined);
const o3 = new Object(null);

Object()-Konstruktor verwenden, um Primitives in ein Object des jeweiligen Typs umzuwandeln

// Equivalent to const o1 = new Boolean(true)
const o1 = new Object(true);

// No equivalent because BigInt() can't be called as a constructor,
// and calling it as a regular function won't create an object
const o2 = new Object(1n);

Object-Prototypen

const current = Object.prototype.valueOf;

// Since my property "-prop-value" is cross-cutting and isn't always
// on the same prototype chain, I want to modify Object.prototype:
Object.prototype.valueOf = function (...args) {
  if (Object.hasOwn(this, "-prop-value")) {
    return this["-prop-value"];
  }
  // It doesn't look like one of my objects, so let's fall back on
  // the default behavior by reproducing the current behavior as best we can.
  // The apply behaves like "super" in some other languages.
  // Even though valueOf() doesn't take arguments, some other hook may.
  return current.apply(this, args);
};

propertyIsEnumerable direkt auf einer Instanz

const obj = {
  foo: 1,
  // You should not define such a method on your own object,
  // but you may not be able to prevent it from happening if
  // you are receiving the object from external input
  propertyIsEnumerable() {
    return false;
  },
};

obj.propertyIsEnumerable("foo"); // false; unexpected result
Object.prototype.propertyIsEnumerable.call(obj, "foo"); // true; expected result

null-prototype-Objekte erzeugen

const obj = Object.create(null);
const obj2 = { __proto__: null };

Fehlendes toString bei null-Prototyp

const normalObj = {}; // create a normal object
const nullProtoObj = Object.create(null); // create an object with "null" prototype

console.log(`normalObj is: ${normalObj}`); // shows "normalObj is: [object Object]"
console.log(`nullProtoObj is: ${nullProtoObj}`); // throws error: Cannot convert object to primitive value

alert(normalObj); // shows [object Object]
alert(nullProtoObj); // throws error: Cannot convert object to primitive value

Weitere Methoden schlagen fehl

normalObj.valueOf(); // shows {}
nullProtoObj.valueOf(); // throws error: nullProtoObj.valueOf is not a function

normalObj.hasOwnProperty("p"); // shows "true"
nullProtoObj.hasOwnProperty("p"); // throws error: nullProtoObj.hasOwnProperty is not a function

normalObj.constructor; // shows "Object() { [native code] }"
nullProtoObj.constructor; // shows "undefined"

toString wieder hinzufügen

nullProtoObj.toString = Object.prototype.toString; // since new object lacks toString, add the original generic one back

console.log(nullProtoObj.toString()); // shows "[object Object]"
console.log(`nullProtoObj is: ${nullProtoObj}`); // shows "nullProtoObj is: [object Object]"

Objekt als Map: unerwartete Treffer durch Object.prototype

const ages = { alice: 18, bob: 27 };

function hasPerson(name) {
  return name in ages;
}

function getAge(name) {
  return ages[name];
}

hasPerson("hasOwnProperty"); // true
getAge("toString"); // [Function: toString]

null-Prototyp als sichere Map

const ages = Object.create(null, {
  alice: { value: 18, enumerable: true },
  bob: { value: 27, enumerable: true },
});

hasPerson("hasOwnProperty"); // false
getAge("toString"); // undefined

Prototype-Pollution

const user = {};

// A malicious script:
Object.prototype.authenticated = true;

// Unexpectedly allowing unauthenticated user to pass through
if (user.authenticated) {
  // access confidential data
}

// Wichtig · Fallstricke

Das Ändern der prototype-Eigenschaft eines beliebigen eingebauten Konstruktors gilt als schlechte Praxis und gefährdet die Vorwärtskompatibilität.