Der er to slags objektegenskaber.

Den første slags er dataegenskaber. Vi ved allerede, hvordan man arbejder med dem. Alle egenskaber, som vi har brugt indtil nu, var dataegenskaber.

Den anden type egenskaber er noget nyt. Det er accessor-egenskaber. De er i bund og grund funktioner, der udføres ved at få og sætte en værdi, men som ligner almindelige egenskaber for en ekstern kode.

Getters og setters

Accessor-properties er repræsenteret af “getter”- og “setter”-metoder. I en objektlitteratur betegnes de med get og set:

let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};

Getteren virker, når obj.propName læses, setteren – når den tildeles.

For eksempel har vi et user-objekt med name og surname:

let user = { name: "John", surname: "Smith"};

Nu vil vi tilføje en fullName-egenskab, der skal være "John Smith". Vi ønsker naturligvis ikke at copy-paste eksisterende oplysninger, så vi kan implementere den som en accessor:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith

Udefra ser en accessor-egenskab ud som en almindelig egenskab. Det er idéen med accessor-egenskaber. Vi kalder ikke user.fullName som en funktion, vi læser den normalt: getteren kører bag kulisserne.

Som det er nu, har fullName kun en getter. Hvis vi forsøger at tildele user.fullName=, vil der opstå en fejl:

let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)

Lad os rette det ved at tilføje en setter til user.fullName:

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

Som resultat har vi en “virtuel” egenskab fullName. Den kan læses og skrives.

Accessor-deskriptorer

Deskriptorer for accessor-egenskaber er forskellige fra dem for dataegenskaber.

For accessor-egenskaber er der ingen value eller writable, men i stedet er der get og set-funktioner.

Det vil sige, at en accessor-descriptor kan have:

  • get – en funktion uden argumenter, der virker, når en egenskab læses,
  • set – en funktion med ét argument, der kaldes, når egenskaben sættes,
  • enumerable – det samme som for dataegenskaber,
  • configurable – det samme som for dataegenskaber.

For eksempel kan vi for at oprette en accessor fullName med defineProperty videregive en deskriptor med get og set for at oprette en accessor fullName med defineProperty:

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

Bemærk, at en egenskab enten kan være en accessor (har get/set metoder) eller en dataegenskab (har en value), ikke begge dele.

Hvis vi forsøger at levere både get og value i den samme deskriptor, vil der opstå en fejl:

// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});

Smartere getters/setters

Getters/setters kan bruges som wrappers over “rigtige” egenskabsværdier for at få mere kontrol over operationer med dem.

For eksempel, hvis vi ønsker at forbyde for korte navne for user, kan vi have en setter name og beholde værdien i en separat egenskab _name:

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...

Så er navnet gemt i _name-egenskaben, og adgangen sker via getter og setter.

Teknisk set kan ekstern kode få direkte adgang til navnet ved at bruge user._name. Men der er en almindeligt kendt konvention om, at egenskaber, der starter med en understregning "_", er interne og ikke bør berøres udefra.

Brug for kompatibilitet

En af de store anvendelsesmuligheder for accessorer er, at de gør det muligt at tage kontrol over en “almindelig” dataegenskab på et hvilket som helst tidspunkt ved at erstatte den med en getter og en setter og justere dens adfærd.

Forestil dig, at vi begyndte at implementere brugerobjekter ved hjælp af dataegenskaber name og age:

function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25

…Men før eller siden kan tingene ændre sig. I stedet for age beslutter vi måske at gemme birthday, fordi det er mere præcist og praktisk:

function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));

Hvad gør vi nu med den gamle kode, der stadig bruger age-egenskaben?

Vi kan forsøge at finde alle sådanne steder og rette dem, men det tager tid og kan være svært at gøre, hvis den pågældende kode bruges af mange andre mennesker. Og desuden er age en rar ting at have i user, ikke?

Lad os beholde den.

Gennem at tilføje en getter til age løser vi problemet:

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

Nu virker den gamle kode også, og vi har fået en dejlig ekstra egenskab.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.