Det finns två typer av objektegenskaper.

Den första typen är dataegenskaper. Vi vet redan hur man arbetar med dem. Alla egenskaper som vi hittills har använt var dataegenskaper.

Den andra typen av egenskaper är något nytt. Det är accessoregenskaper. De är i huvudsak funktioner som exekverar vid hämtning och inställning av ett värde, men ser ut som vanliga egenskaper för en extern kod.

Getters och setters

Accessor properties representeras av ”getter”- och ”setter”-metoder. I en objektlitteratur betecknas de av get och 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 }};

Gettern fungerar när obj.propName läses, settern – när den tilldelas.

Till exempel har vi ett user objekt med name och surname:

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

Nu vill vi lägga till en fullName egenskap, som ska vara "John Smith". Naturligtvis vill vi inte kopiera-klistra in befintlig information, så vi kan implementera den som en accessor:

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

Från utsidan ser en accessor-egenskap ut som en vanlig egenskap. Det är tanken med accessor properties. Vi anropar inte user.fullName som en funktion, vi läser den normalt: gettern körs bakom kulisserna.

Från och med nu har fullName bara en getter. Om vi försöker tilldela user.fullName= blir det ett fel:

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

Låt oss åtgärda det genom att lägga till en setter för 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 ”virtuell” egenskap fullName. Den är läsbar och skrivbar.

Accessor deskriptorer

Deskriptorer för accessoregenskaper skiljer sig från dem för dataegenskaper.

För accessoregenskaper finns det ingen value eller writable, utan istället finns det get och set funktioner.

Det vill säga, en accessorbeskrivare kan ha:

  • get – en funktion utan argument, som fungerar när en egenskap läses,
  • set – en funktion med ett argument, som anropas när egenskapen sätts,
  • enumerable – samma som för dataegenskaper,
  • configurable – samma som för dataegenskaper.

För att till exempel skapa en accessor fullName med defineProperty kan vi skicka en deskriptor med get och set:

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

Observera att en egenskap kan vara antingen en accessor (har get/set metoder) eller en dataegenskap (har en value), inte båda.

Om vi försöker tillhandahålla både get och value i samma deskriptor kommer det att uppstå ett fel:

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

Smarta getters/setters

Getters/setters kan användas som omslag över ”riktiga” egenskapsvärden för att få mer kontroll över operationer med dem.

Om vi till exempel vill förbjuda alltför korta namn för user kan vi ha en setter name och behålla värdet i en separat egenskap _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...

Namnet lagras alltså i egenskap _name och åtkomsten sker via getter och setter.

Tekniskt sett kan extern kod få direkt åtkomst till namnet genom att använda user._name. Men det finns en allmänt känd konvention om att egenskaper som börjar med ett understreck "_" är interna och inte bör beröras från utsidan av objektet.

Användning för kompatibilitet

En av de stora användningsområdena för accessorerna är att de gör det möjligt att ta kontroll över en ”vanlig” dataegenskap när som helst genom att ersätta den med en getter och en setter och justera dess beteende.

Föreställ dig att vi började implementera användarobjekt med hjälp av dataegenskaper name och age:

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

…Men förr eller senare kan saker och ting förändras. Istället för age kanske vi bestämmer oss för att lagra birthday, eftersom det är mer exakt och bekvämt:

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

Vad ska vi nu göra med den gamla koden som fortfarande använder age-egenskapen?

Vi kan försöka hitta alla sådana ställen och rätta till dem, men det tar tid och kan vara svårt att göra om den koden används av många andra människor. Dessutom är age en trevlig sak att ha i user, eller hur?

Låt oss behålla den.

Att lägga till en getter för age löser 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 fungerar den gamla koden också och vi har en trevlig extra egenskap.

Lämna ett svar

Din e-postadress kommer inte publiceras.