Start · Sprachen · JavaScript · Referenz · Object.defineProperty()

Object.defineProperty()

Methode

Die statische Methode <code>Object.defineProperty()</code> definiert eine neue Eigenschaft direkt auf einem Objekt oder ändert eine bestehende und gibt das Objekt zurück.

Kategorie: method-property

Signatur

Object.defineProperty(obj, prop, descriptor)

Beschreibung

Die statische Methode Object.defineProperty() definiert eine neue Eigenschaft direkt auf einem Objekt oder verändert eine bestehende Eigenschaft eines Objekts und gibt das Objekt zurück.

Object.defineProperty() ermöglicht eine präzise Ergänzung oder Änderung einer Eigenschaft eines Objekts. Normales Hinzufügen von Eigenschaften über Zuweisung erzeugt Eigenschaften, die während der Aufzählung (for...in, Object.keys() usw.) sichtbar sind, deren Werte geändert und die gelöscht werden können. Diese Methode erlaubt es, diese zusätzlichen Details gegenüber ihren Standardwerten anzupassen. Standardmäßig sind Eigenschaften, die mit Object.defineProperty() hinzugefügt werden, weder beschreibbar (writable), noch aufzählbar (enumerable), noch konfigurierbar (configurable). Zusätzlich verwendet Object.defineProperty() die interne Methode [[DefineOwnProperty]] anstelle von [[Set]], sodass keine setter aufgerufen werden, selbst wenn die Eigenschaft bereits vorhanden ist.

Property-Descriptoren in Objekten gibt es in zwei Hauptausprägungen: data descriptors und accessor descriptors. Ein data descriptor ist eine Eigenschaft mit einem Wert, der beschreibbar sein kann oder nicht. Ein accessor descriptor ist eine Eigenschaft, die durch ein Paar aus getter- und setter-Funktionen beschrieben wird. Ein Descriptor muss eine dieser beiden Ausprägungen sein; er kann nicht beides sein.

Sowohl data- als auch accessor-Descriptoren sind Objekte. Sie teilen sich die folgenden optionalen Schlüssel (Hinweis: die hier genannten Standardwerte gelten beim Definieren von Eigenschaften mit Object.defineProperty()):

  • configurable: wenn dies auf false gesetzt ist,
    • kann der Typ dieser Eigenschaft nicht zwischen data property und accessor property gewechselt werden,
    • kann die Eigenschaft nicht gelöscht werden,
    • können andere Attribute ihres Descriptors nicht geändert werden (wenn es sich jedoch um einen data descriptor mit writable: true handelt, kann der value geändert werden und writable kann auf false gesetzt werden).
    Standard: false.
  • enumerable: true genau dann, wenn diese Eigenschaft bei der Aufzählung der Eigenschaften des zugehörigen Objekts erscheint. Standard: false.

Ein data descriptor hat zusätzlich die folgenden optionalen Schlüssel:

  • value: Der Wert, der der Eigenschaft zugeordnet ist. Kann jeder gültige JavaScript-Wert sein (Zahl, Objekt, Funktion usw.). Standard: undefined.
  • writable: true, wenn der Wert der Eigenschaft mit einem Zuweisungsoperator geändert werden darf. Standard: false.

Ein accessor descriptor hat zusätzlich die folgenden optionalen Schlüssel:

  • get: Eine Funktion, die als getter der Eigenschaft dient, oder undefined, wenn kein getter vorhanden ist. Beim Zugriff auf die Eigenschaft wird diese Funktion ohne Argumente und mit this auf das Objekt gesetzt, über das auf die Eigenschaft zugegriffen wird (dies ist aufgrund von Vererbung möglicherweise nicht das Objekt, auf dem die Eigenschaft definiert ist). Der Rückgabewert wird als Wert der Eigenschaft verwendet. Standard: undefined.
  • set: Eine Funktion, die als setter der Eigenschaft dient, oder undefined, wenn kein setter vorhanden ist. Bei der Zuweisung an die Eigenschaft wird diese Funktion mit einem Argument (dem der Eigenschaft zugewiesenen Wert) und mit this auf das Objekt gesetzt, über das der Eigenschaft zugewiesen wird. Standard: undefined.

Enthält ein Descriptor keinen der Schlüssel value, writable, get und set, wird er als data descriptor behandelt. Ist ein Descriptor sowohl data descriptor (weil er value oder writable enthält) als auch accessor descriptor (weil er get oder set enthält), wird eine Ausnahme geworfen.

Diese Attribute sind nicht notwendigerweise eigene Eigenschaften des Descriptors. Auch geerbte Eigenschaften werden berücksichtigt. Um sicherzustellen, dass diese Standardwerte erhalten bleiben, können Sie vorhandene Objekte in der Prototypkette des Descriptor-Objekts vorab einfrieren, alle Optionen explizit angeben oder ein Objekt mit null-Prototyp erstellen.

Wenn die Eigenschaft bereits existiert, versucht Object.defineProperty(), die Eigenschaft entsprechend den Werten im Descriptor und der aktuellen Konfiguration der Eigenschaft zu ändern.

Hatte der alte Descriptor sein Attribut configurable auf false gesetzt, gilt die Eigenschaft als non-configurable. Es ist nicht möglich, ein Attribut einer nicht konfigurierbaren accessor property zu ändern, und es ist nicht möglich, zwischen data- und accessor-Property-Typ zu wechseln. Für data properties mit writable: true ist es möglich, den Wert zu ändern und das Attribut writable von true auf false zu setzen. Ein TypeError wird geworfen, wenn versucht wird, Attribute einer nicht konfigurierbaren Eigenschaft zu ändern (außer value und writable, sofern erlaubt), es sei denn, es wird derselbe Wert wie der ursprüngliche Wert einer data property definiert.

Wenn die aktuelle Eigenschaft konfigurierbar ist, wird sie durch das Setzen eines Attributs auf undefined effektiv gelöscht. Wenn zum Beispiel o.k eine accessor property ist, entfernt Object.defineProperty(o, "k", { set: undefined }) den setter, sodass k nur noch einen getter hat und schreibgeschützt wird. Fehlt ein Attribut im neuen Descriptor, wird der Attributwert des alten Descriptors beibehalten (er wird nicht implizit auf undefined neu gesetzt). Es ist möglich, zwischen data- und accessor property zu wechseln, indem ein Descriptor einer anderen „Ausprägung" angegeben wird. Ist der neue Descriptor beispielsweise ein data descriptor (mit value oder writable), werden die Attribute get und set des ursprünglichen Descriptors beide verworfen.

Parameter

Name Typ Default Beschreibung
$obj Pflicht Object Das Objekt, auf dem die Eigenschaft definiert werden soll.
$prop Pflicht string | Symbol Ein String oder Symbol, das den Schlüssel der zu definierenden oder zu ändernden Eigenschaft angibt.
$descriptor Pflicht Object Der Descriptor für die zu definierende oder zu ändernde Eigenschaft.

Rückgabewert

Typ
Object
Beschreibung
Das an die Funktion übergebene Objekt, mit der angegebenen Eigenschaft hinzugefügt oder geändert.

Beispiele

Eine Eigenschaft erstellen

const o = {}; // Creates a new object

// Example of an object property added
// with defineProperty with a data property descriptor
Object.defineProperty(o, "a", {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true,
});
// 'a' property exists in the o object and its value is 37

// Example of an object property added
// with defineProperty with an accessor property descriptor
let bValue = 38;
Object.defineProperty(o, "b", {
  get() {
    return bValue;
  },
  set(newValue) {
    bValue = newValue;
  },
  enumerable: true,
  configurable: true,
});
o.b; // 38
// 'b' property exists in the o object and its value is 38
// The value of o.b is now always identical to bValue,
// unless o.b is redefined

// You cannot try to mix both:
Object.defineProperty(o, "conflict", {
  value: 0x9f91102,
  get() {
    return 0xdeadbeef;
  },
});
// throws a TypeError: value appears
// only in data descriptors,
// get appears only in accessor descriptors

Writable-Attribut

const o = {}; // Creates a new object

Object.defineProperty(o, "a", {
  value: 37,
  writable: false,
});

console.log(o.a); // 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // 37; the assignment didn't work

// strict mode
(() => {
  "use strict";
  const o = {};
  Object.defineProperty(o, "b", {
    value: 2,
    writable: false,
  });
  o.b = 3; // throws TypeError: "b" is read-only
  return o.b; // returns 2 without the line above
})();

Enumerable-Attribut

const o = {};
Object.defineProperty(o, "a", {
  value: 1,
  enumerable: true,
});
Object.defineProperty(o, "b", {
  value: 2,
  enumerable: false,
});
Object.defineProperty(o, "c", {
  value: 3,
}); // enumerable defaults to false
o.d = 4; // enumerable defaults to true when creating a property by setting it
Object.defineProperty(o, Symbol.for("e"), {
  value: 5,
  enumerable: true,
});
Object.defineProperty(o, Symbol.for("f"), {
  value: 6,
  enumerable: false,
});

for (const i in o) {
  console.log(i);
}
// Logs 'a' and 'd' (always in that order)

Object.keys(o); // ['a', 'd']

o.propertyIsEnumerable("a"); // true
o.propertyIsEnumerable("b"); // false
o.propertyIsEnumerable("c"); // false
o.propertyIsEnumerable("d"); // true
o.propertyIsEnumerable(Symbol.for("e")); // true
o.propertyIsEnumerable(Symbol.for("f")); // false

const p = { ...o };
p.a; // 1
p.b; // undefined
p.c; // undefined
p.d; // 4
p[Symbol.for("e")]; // 5
p[Symbol.for("f")]; // undefined

Configurable-Attribut (accessor property)

const o = {};
Object.defineProperty(o, "a", {
  get() {
    return 1;
  },
  configurable: false,
});

Object.defineProperty(o, "a", {
  configurable: true,
}); // throws a TypeError
Object.defineProperty(o, "a", {
  enumerable: true,
}); // throws a TypeError
Object.defineProperty(o, "a", {
  set() {},
}); // throws a TypeError (set was undefined previously)
Object.defineProperty(o, "a", {
  get() {
    return 1;
  },
}); // throws a TypeError
// (even though the new get does exactly the same thing)
Object.defineProperty(o, "a", {
  value: 12,
}); // throws a TypeError
// ('value' can be changed when 'configurable' is false, but only when the property is a writable data property)

console.log(o.a); // 1
delete o.a; // Nothing happens; throws an error in strict mode
console.log(o.a); // 1

Non-configurable, aber writable data property

const o = {};
Object.defineProperty(o, "b", {
  writable: true,
  configurable: false,
});
console.log(o.b); // undefined
Object.defineProperty(o, "b", {
  value: 1,
}); // Even when configurable is false, because the object is writable, we may still replace the value
console.log(o.b); // 1
o.b = 2; // We can change the value with assignment operators as well
console.log(o.b); // 2
// Toggle the property's writability
Object.defineProperty(o, "b", {
  writable: false,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // TypeError: because the property is neither writable nor configurable, it cannot be modified
// At this point, there's no way to further modify 'b'
// or restore its writability

Configurable, aber non-writable data property

const o = {};
Object.defineProperty(o, "b", {
  writable: false,
  configurable: true,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // We can replace the value with defineProperty
console.log(o.b); // 1
o.b = 2; // throws TypeError in strict mode: cannot change a non-writable property's value with assignment

Non-configurable und non-writable data property

const o = {};
Object.defineProperty(o, "b", {
  writable: false,
  configurable: false,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // TypeError: the property cannot be modified because it is neither writable nor configurable.

Eigenschaften hinzufügen und Standardwerte

const o = {};

o.a = 1;
// is equivalent to:
Object.defineProperty(o, "a", {
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true,
});

// On the other hand,
Object.defineProperty(o, "a", { value: 1 });
// is equivalent to:
Object.defineProperty(o, "a", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false,
});

Benutzerdefinierte setter und getter

function Archiver() {
  let temperature = null;
  const archive = [];

  Object.defineProperty(this, "temperature", {
    get() {
      console.log("get!");
      return temperature;
    },
    set(value) {
      temperature = value;
      archive.push({ val: temperature });
    },
  });

  this.getArchive = () => archive;
}

const arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

Getter, der immer denselben Wert liefert

const pattern = {
  get() {
    return "I always return this string, whatever you have assigned";
  },
  set() {
    this.myName = "this is my name string";
  },
};

function TestDefineSetAndGet() {
  Object.defineProperty(this, "myProperty", pattern);
}

const instance = new TestDefineSetAndGet();
instance.myProperty = "test";
console.log(instance.myProperty);
// I always return this string, whatever you have assigned

console.log(instance.myName); // this is my name string

Vererbung von Eigenschaften

function MyClass() {}

let value;
Object.defineProperty(MyClass.prototype, "x", {
  get() {
    return value;
  },
  set(x) {
    value = x;
  },
});

const a = new MyClass();
const b = new MyClass();
a.x = 1;
console.log(b.x); // 1

Vererbung mit this

function MyClass() {}

Object.defineProperty(MyClass.prototype, "x", {
  get() {
    return this.storedX;
  },
  set(x) {
    this.storedX = x;
  },
});

const a = new MyClass();
const b = new MyClass();
a.x = 1;
console.log(b.x); // undefined

Geerbte data properties

function MyClass() {}

MyClass.prototype.x = 1;
Object.defineProperty(MyClass.prototype, "y", {
  writable: false,
  value: 1,
});

const a = new MyClass();
a.x = 2;
console.log(a.x); // 2
console.log(MyClass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(MyClass.prototype.y); // 1