Es gibt zwei Arten von Objekteigenschaften.
Die erste Art sind Dateneigenschaften. Wir wissen bereits, wie man mit ihnen arbeitet. Alle Eigenschaften, die wir bis jetzt verwendet haben, waren Dateneigenschaften.
Die zweite Art von Eigenschaften ist etwas Neues. Es sind Accessor-Eigenschaften. Sie sind im Wesentlichen Funktionen, die beim Erhalten und Setzen eines Wertes ausgeführt werden, aber für einen externen Code wie normale Eigenschaften aussehen.
Getter und Setter
Zugriffseigenschaften werden durch „Getter“- und „Setter“-Methoden dargestellt. In einem Objektliteral werden sie mit get
und set
bezeichnet:
let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};
Der Getter funktioniert, wenn obj.propName
gelesen wird, der Setter – wenn er zugewiesen wird.
Wir haben zum Beispiel ein user
-Objekt mit name
und surname
:
let user = { name: "John", surname: "Smith"};
Nun wollen wir eine fullName
-Eigenschaft hinzufügen, die "John Smith"
sein soll. Natürlich wollen wir keine vorhandenen Informationen kopieren, also können wir sie als Accessor implementieren:
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith
Von außen sieht eine Accessor-Eigenschaft wie eine normale aus. Das ist die Idee von Accessor-Eigenschaften. Wir rufen user.fullName
nicht als Funktion auf, sondern lesen sie ganz normal: der Getter läuft im Hintergrund.
Ab sofort hat fullName
nur einen Getter. Wenn wir versuchen, user.fullName=
zuzuweisen, wird es einen Fehler geben:
let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)
Lassen Sie uns das beheben, indem wir einen Setter für user.fullName
hinzufügen:
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { = value.split(" "); }};// set fullName is executed with the given value.user.fullName = "Alice Cooper";alert(user.name); // Alicealert(user.surname); // Cooper
Als Ergebnis haben wir eine „virtuelle“ Eigenschaft fullName
. Sie ist lesbar und beschreibbar.
Accessor-Deskriptoren
Deskriptoren für Accessor-Eigenschaften unterscheiden sich von denen für Dateneigenschaften.
Für Accessor-Eigenschaften gibt es keine value
oder writable
, sondern get
und set
Funktionen.
Das heißt, ein Accessor-Deskriptor kann haben:
-
get
– eine Funktion ohne Argumente, die arbeitet, wenn eine Eigenschaft gelesen wird, -
set
– eine Funktion mit einem Argument, die aufgerufen wird, wenn die Eigenschaft gesetzt wird, -
enumerable
– wie bei den Dateneigenschaften, -
configurable
– wie bei den Dateneigenschaften.
Um zum Beispiel einen Accessor fullName
mit defineProperty
zu erstellen, können wir einen Deskriptor mit get
und set
übergeben:
let user = { name: "John", surname: "Smith"};Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { = value.split(" "); }});alert(user.fullName); // John Smithfor(let key in user) alert(key); // name, surname
Bitte beachten Sie, dass eine Eigenschaft entweder ein Accessor (hat get/set
Methoden) oder eine Dateneigenschaft (hat ein value
) sein kann, nicht beides.
Wenn wir versuchen, sowohl get
als auch value
in demselben Deskriptor anzugeben, wird ein Fehler auftreten:
// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});
Smartere Getter/Setter
Getter/Setter können als Wrapper über „echten“ Eigenschaftswerten verwendet werden, um mehr Kontrolle über Operationen mit ihnen zu erhalten.
Wenn wir zum Beispiel zu kurze Namen für user
verbieten wollen, können wir einen Setter name
haben und den Wert in einer separaten Eigenschaft _name
aufbewahren:
let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; }};user.name = "Pete";alert(user.name); // Peteuser.name = ""; // Name is too short...
So wird der Name in der Eigenschaft _name
gespeichert, und der Zugriff erfolgt über Getter und Setter.
Technisch gesehen kann externer Code direkt auf den Namen zugreifen, indem er user._name
verwendet. Aber es gibt eine weit verbreitete Konvention, dass Eigenschaften, die mit einem Unterstrich "_"
beginnen, intern sind und nicht von außerhalb des Objekts berührt werden sollten.
Aus Kompatibilitätsgründen verwenden
Einer der großen Vorteile von Accessoren ist, dass sie es erlauben, jederzeit die Kontrolle über eine „normale“ Dateneigenschaft zu übernehmen, indem sie durch einen Getter und einen Setter ersetzt werden und ihr Verhalten verändern.
Stellen Sie sich vor, wir beginnen mit der Implementierung von Benutzerobjekten unter Verwendung der Dateneigenschaften name
und age
:
function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25
…Aber früher oder später können sich die Dinge ändern. Statt age
können wir uns entscheiden, birthday
zu speichern, weil es präziser und bequemer ist:
function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));
Was machen wir nun mit dem alten Code, der immer noch die age
-Eigenschaft verwendet?
Wir können versuchen, alle solchen Stellen zu finden und sie zu korrigieren, aber das braucht Zeit und kann schwierig sein, wenn dieser Code von vielen anderen Leuten benutzt wird. Und außerdem ist age
eine nette Sache, die man in user
haben kann, richtig?
Lasst es uns behalten.
Das Hinzufügen eines Getters für age
löst das Problem:
function User(name, birthday) { this.name = name; this.birthday = birthday; // age is calculated from the current date and birthday Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); } });}let john = new User("John", new Date(1992, 6, 1));alert( john.birthday ); // birthday is availablealert( john.age ); // ...as well as the age
Jetzt funktioniert auch der alte Code und wir haben eine schöne zusätzliche Eigenschaft.