Getters og setters er funktioner eller metoder, der bruges til at hente og indstille værdierne for variabler. Getter-setter-konceptet er almindeligt i computerprogrammering: næsten alle programmeringssprog på højt niveau kommer med et sæt syntaks til at implementere getters og setters, herunder JavaScipt.
Læs også: I dette indlæg vil vi se, hvad getters-setters er, og hvordan man opretter og bruger dem i JavaScript.
- Getters-setters og indkapsling
- Skab getters og setters
- 1. Med metoder
- 2. Med nøgleord
- Hvilken måde er bedst?
- Forebyggelse af overskrivning
- Operationer inden for getters og setters
- Beskyt data med getters og setters
- Ubeskyttede data
- 1. Block scoping
- 2. Funktionsscoping
- 3. Databeskyttelse uden scoping
- Hvornår skal du bruge getters og setters?
Getters-setters og indkapsling
Tanken om getters og setters bliver altid nævnt i forbindelse med indkapsling. Indkapsling kan forstås på to måder.
For det første er det opsætningen af data-getters-setters-trioen for at få adgang til og ændre disse data. Denne definition er nyttig, når der skal udføres nogle operationer, f.eks. validering, på dataene, inden de gemmes eller vises – getters og setters er det perfekte hjemsted for det.
For det andet er der en strengere definition, ifølge hvilken indkapsling sker for at skjule data, for at gøre dem utilgængelige for anden kode, undtagen gennem getters og setters. På den måde ender vi ikke med ved et uheld at overskrive vigtige data med noget anden kode i programmet.
Skab getters og setters
1. Med metoder
Da getters og setters grundlæggende er funktioner, der henter/ændrer en værdi, er der mere end én måde at oprette og bruge dem på. Den første måde er:
var obj = { foo: 'this is the value of foo', getFoo: function() { return this.foo; }, setFoo: function(val) { this.foo = val; }}console.log(obj.getFoo());// "this is the value of foo"obj.setFoo('hello');console.log(obj.getFoo());// "hello"
Dette er den enkleste måde at oprette getters og setters på. Der er en egenskab foo
og der er to metoder: getFoo
og setFoo
til at returnere og tildele en værdi til denne egenskab.
2. Med nøgleord
En mere “officiel” og robust måde at oprette getters og setters på er ved at bruge nøgleordene get
og set
.
For at oprette en getter skal du placere nøgleordet get
foran en funktionsdeklaration, der skal fungere som getter-metode, og bruge nøgleordet set
på samme måde for at oprette en setter. Syntaksen er som følger:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; }}console.log(obj.foo);// "this is the value of foo"obj.foo = 'hello';console.log(obj.foo);// "hello"
Bemærk, at dataene kun kan gemmes under et egenskabsnavn (fooVal
), der er forskelligt fra navnet på getter-setter-metoderne (foo
), fordi en egenskab, der indeholder getter-setteren, ikke også kan indeholde dataene.
Hvilken måde er bedst?
Hvis du vælger at oprette getters og setters med nøgleord, kan du bruge tildelingsoperatoren til at angive dataene og punktoperatoren til at hente dataene, på samme måde som du ville få adgang til/indstille værdien af en almindelig egenskab.
Hvis du vælger den første måde at kode getters og setters på, skal du imidlertid kalde setter- og getter-metoderne ved hjælp af syntaksen for funktionsopkald, fordi de er typiske funktioner (ikke noget særligt som dem, der oprettes ved hjælp af nøgleordene get
og set
).
Der er også en chance for, at du ved et uheld kommer til at tildele en anden værdi til de egenskaber, der indeholdt disse getter-setter-metoder, og mister dem helt! Noget du ikke behøver at bekymre dig om i den senere metode.
Så du kan se, hvorfor jeg sagde, at den anden teknik er mere robust.
Forebyggelse af overskrivning
Hvis du af en eller anden grund foretrækker den første teknik, så gør egenskaberne, der indeholder getter-setter-metoderne, skrivebeskyttede ved at oprette dem ved hjælp af Object.defineProperties
. Egenskaber, der oprettes via Object.defineProperties
, Object.defineProperty
og Reflect.defineProperty
, konfigureres automatisk til writable: false
, hvilket betyder skrivebeskyttet:
/* Overwrite prevention */var obj = { foo: 'this is the value of foo'};Object.defineProperties(obj, { 'getFoo': { value: function () { return this.foo; } }, 'setFoo': { value: function (val) { this.foo = val; } }});obj.getFoo = 66;// getFoo is not going to be overwritten!console.log(obj.getFoo());// "this is the value of foo"
Operationer inden for getters og setters
Når du har indført getters og setters, kan du gå videre og udføre operationer på dataene, før du ændrer eller returnerer dem.
I koden nedenfor sammenkædes dataene i getter-funktionen med en streng, inden de returneres, og i setter-funktionen udføres en validering af, om værdien er et tal eller ej, inden den opdateres n
.
var obj = { n: 67, get id() { return 'The ID is: ' + this.n; }, set id(val) { if (typeof val === 'number') this.n = val; }}console.log(obj.id);// "The ID is: 67"obj.id = 893;console.log(obj.id);// "The ID is: 893"obj.id = 'hello';console.log(obj.id);// "The ID is: 893"
Beskyt data med getters og setters
Så langt har vi dækket brugen af getters og setters i den første kontekst af indkapsling. Lad os gå videre til den anden, dvs. hvordan man skjuler data for kode udefra ved hjælp af getters og setters.
Ubeskyttede data
Oprettelsen af getters og setters betyder ikke, at data kun kan tilgås og ændres via disse metoder. I følgende eksempel ændres den direkte uden at røre ved getter- og settermetoderne:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; }}obj.fooVal = 'hello';console.log(obj.foo);// "hello"
Vi brugte ikke setteren, men ændrede dataene direkte (fooVal
). De data, som vi oprindeligt indstillede inde i obj
, er væk nu! For at forhindre, at dette sker (ved et uheld), har du brug for en vis beskyttelse af dine data. Du kan tilføje det ved at begrænse omfanget af, hvor dine data er tilgængelige. Det kan du gøre ved enten block scoping eller funktion scoping.
1. Block scoping
En måde er at bruge et block scope, inden for hvilket dataene vil blive defineret ved hjælp af nøgleordet let
, som begrænser dets scope til den pågældende blok.
Et block scope kan oprettes ved at placere din kode inden for et par svungne parenteser. Når du opretter et block scope, skal du sørge for at efterlade en kommentar over det og bede om at lade parenteserne være i fred, så ingen ved en fejltagelse fjerner parenteserne og tror, at de er nogle ekstra overflødige parenteser i koden eller tilføjer en etiket til block scope.
/* BLOCK SCOPE, leave the braces alone! */{let fooVal = 'this is the value of foo';var obj = { get foo() { return fooVal; }, set foo(val) { fooVal = val } }}fooVal = 'hello';// not going to affect the fooVal inside the blockconsole.log(obj.foo);// "this is the value of foo"
Ændring/oprettelse af fooVal
uden for blokken vil ikke påvirke de fooVal
, der henvises til inden for gettersættersætters.
2. Funktionsscoping
Den mere almindelige måde at beskytte dataene med scoping er ved at holde dataene inde i en funktion og returnere et objekt med getters og setters fra denne funktion.
function myobj(){ var fooVal = 'this is the value of foo'; return { get foo() { return fooVal; }, set foo(val) { fooVal = val } }}fooVal = 'hello';// not going to affect our original fooValvar obj = myobj();console.log(obj.foo);// "this is the value of foo"
Objektet (med foo()
getter-setteren inde i det), der returneres af myobj()
-funktionen, gemmes i obj
, og derefter bruges obj
til at kalde getteren og setteren.
3. Databeskyttelse uden scoping
Der er også en anden måde, hvorpå du kan beskytte dine data mod at blive overskrevet uden at begrænse deres anvendelsesområde. Logikken bag det går sådan her: Hvordan kan man ændre et stykke data, hvis man ikke ved, hvad det hedder?
Hvis dataene har et ikke så let reproducerbart variabel/egenskabsnavn, er der gode chancer for, at ingen (selv ikke os selv) ender med at overskrive det ved at tildele en eller anden værdi til dette variabel/egenskabsnavn.
var obj = { s89274934764: 'this is the value of foo', get foo() { return this.s89274934764; }, set foo(val) { this.s89274934764 = val; }}console.log(obj.foo);// "this is the value of foo"
Se, det er en måde, man kan løse tingene på. Selv om det navn, jeg valgte, ikke er et rigtig godt navn, kan du også bruge tilfældige værdier eller symboler til at oprette egenskabsnavne, som Derick Bailey foreslår i dette blogindlæg. Hovedformålet er at holde dataene skjult for anden kode og lade et getter-setter-par til at få adgang til/opdatere dem.
Hvornår skal du bruge getters og setters?
Nu kommer det store spørgsmål: Skal du begynde at tildele getters og setters til alle dine data nu?
Hvis du skjuler data, så er der ikke noget andet valg.
Men hvis dine data kan ses af anden kode, er det så stadig nødvendigt at bruge getters setters, bare for at bundle dem med kode, der udfører nogle operationer på dem? Jeg vil sige ja. Kode lægger sig meget hurtigt op. Ved at oprette mikroenheder af individuelle data med sin egen getter-setter får du en vis uafhængighed til at arbejde på de nævnte data uden at påvirke andre dele af koden.
Læs også: 10 grunde til, at du har brug for kodeoptimering