Signatur
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()undtoLocaleString()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 werdenvalueOf()undtoString()ü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 AlternativenObject.defineProperty()undObject.getOwnPropertyDescriptor().- Die Eigenschaft
__proto__ist veraltet und sollte nicht verwendet werden. Die AlternativenObject.getPrototypeOf()undObject.setPrototypeOf()sind statische Methoden. - Die Methoden
propertyIsEnumerable()undhasOwnProperty()können jeweils durch die statischen MethodenObject.getOwnPropertyDescriptor()bzw.Object.hasOwn()ersetzt werden. - Die Methode
isPrototypeOf()kann meist durchinstanceofersetzt werden, sofern man dieprototype-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
groupsundindices.groupsdes Ergebnisses vonRegExp.prototype.exec() Array.prototype[Symbol.unscopables](alle[Symbol.unscopables]-Objekte solltennull-Prototyp haben)import.meta- Modul-Namespace-Objekte, erhalten über
import * as ns from "module";oderimport()
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.
undefinedundnullwerfen einenTypeError.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, umxumzuwandeln.- Die Funktion
Object():Object(x)verwendet denselben Algorithmus zur Umwandlung vonx, außer dassundefinedundnullkeinenTypeErrorwerfen, sondern ein einfaches Objekt zurückgeben.
Stellen, die Object Coercion nutzen, sind u. a.:
- Der
object-Parameter vonfor...in-Schleifen. - Der
this-Wert vonArray-Methoden. - Parameter von
Object-Methoden wieObject.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ährendnullundundefineddurch 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
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
}