
16 minute read
4.2 Metoder
EKSEMPEL
Endre verdien til et felt
Vi kan endre verdiene til feltene i et objekt etter at vi har opprettet objektet. Dette gjør vi ved å skrive objektnavn.feltnavn = nyVerdi. I følgende kodesnutt vil Fido bli omdøpt til Pluto.
1 <!-- hund-test.svelte --> 2 <script> 3 import { Hund } from "./_hund.js"
4
5 let hundeobjekt = new Hund("Fido") 6 hundeobjekt.navn = "Pluto" 7 </script>
8 9 <h1>Flink bisk, {hundeobjekt.navn}!</h1>
I Svelte går det an å binde verdiene i HTML-attributter sammen med verdiene som ligger lagret i variabler, ved å skrive
bind:attributt={variabelnavn}. På samme måte kan vi også binde et attributt sammen med verdien til et felt ved å skrive
bind:attributt={variabelnavn.feltnavn}. I koden nedenfor har vi lagt til et input-felt og bundet det til navnet til hundeobjekt. Når vi skriver inn et annet navn, for eksempel Nala, endres samtidig verdien til navn-feltet i hundeobjekt.
1 <!-- hund-test.svelte --> 2 <script> 3 import { Hund } from "./_hund.js"
4
5 let hundeobjekt = new Hund("Fido") 6 </script>
7 8 <h1>Flink bisk, {hundeobjekt.navn}!</h1> 9 <label>Hundenavn: <input bind:value={hundeobjekt.navn} /></label>

I forrige delkapittel brukte vi bare klasser og objekter til å lagre data. Dette kunne vi fått til med færre kodelinjer dersom vi brukte teknikker vi allerede kjenner til, slik som JavaScript-objektene vi skriver med krøllparenteser:
{ foo: 1, bar: "en streng" }.
DISKUTER
Hvordan ville du ha programmert eksemplene med klassene Hund og Sirkel for å oppnå samme resultat uten å bruke objektorientert programmering?
Forskjellen mellom objekter i objektorientert programmering og JavaScript-objekter er at vi kan bygge inn funksjoner i objektorientert-programmering-objektene. Disse funksjonene kalles metoder. Vi kan blant annet bruke metoder til å styre hvordan vi oppdaterer data i objektene, slik at feltene ikke inneholder data som er i uoverensstemmelse med hverandre.
SØRGE FOR AT DATA IKKE KOMMER I UOVERENSSTEMMELSE MED SETT-METODER
Som nevnt er en av fordelene med objektorientert programmering muligheten til å ha metoder i objektene. Den viktigste forskjellen mellom metoder og frittstående funksjoner er at en metode har tilgang til å endre feltene i et objekt og hente data fra dem. Metoder får tilgang til disse dataene ved å bruke nøkkelordet this.
På neste side ser vi hvordan vi kan bruke en såkalt sett-metode for å sette radiusen til en ny verdi. Sett-metoder oppdaterer verdier og gjør alt annet «opprydningsarbeid» som må gjøres, samtidig. Dette arbeidet går som regel ut på å endre verdien til andre felter.
EKSEMPEL
Hvis feltene i en klasse inneholder data som avhenger av hverandre, kan disse komme i uoverensstemmelse med hverandre dersom vi oppdaterer ett felt og ikke et annet. Hvis vi ser på sirkeleksemplet, er det lett å havne i situasjoner der arealet, omkretsen og radiusen ikke stemmer overens hvis vi bare oppdaterer ett av feltene, slik vi gjør i koden nedenfor.
1 <script> 2 class Sirkel {
3 constructor(radius) {
4 this.radius = radius
5 this.omkrets = 2 * Math.PI * radius
6 this.areal = Math.PI * radius * radius
7 8 } 9 10 let minSirkel = new Sirkel(10) 11 // Oppdaterer radius, men glemmer areal og omkrets. 12 minSirkel.radius = 1 13 </script>
14 15 <div> 16 Vi har en sirkel med radius {minSirkel.radius}. <br /> 17 <!-- Areal og omkrets stemmer ikke med radius. --> 18 Arealet av sirkelen er {minSirkel.areal}. <br /> 19 Omkretsen av sirkelen er {minSirkel.omkrets}. 20 </div>

I koden ser vi at radiusen først ble satt til å være 10 og deretter endret til 1. Nå stemmer ikke lenger radiusen overens med arealet og omkretsen til sirkelen.
Nå stemmer arealet og omkretsen med radiusen. I koden nedenfor har vi lagt til metoden settRadius i klassen Sirkel. Denne metoden endrer radiusen og regner ut verdien til de to andre feltene.
Legg merke til at metoden starter med et verb. Det er en utbredt skikk å starte metodenavn med et verb, mens feltnavn bør starte med et substantiv. Dette hjelper leseren til å forstå at metoden er en metode og ikke et felt. Sannsynligvis er verbet nok til at leseren skjønner alt metoden gjør, uten å lese kodelinjene i den.
I den siste linjen i script-blokken kaller vi på metoden ved å skrive
minSirkel.settRadius(1).
1 <!-- sirkel-med-setter.svelte --> 2 <script> 3 class Sirkel {
4 constructor(radius) {
5 this.radius = radius
6 this.omkrets = 2 * Math.PI * radius
7 8 9 10 11 12 13 14 15 } 16 this.areal = Math.PI * radius * radius
settRadius(radius) { this.radius = radius this.omkrets = 2 * Math.PI * radius this.areal = Math.PI * radius * radius
17 let minSirkel = new Sirkel(10) 18 minSirkel.settRadius(1) 19 </script>
20 21 <div> 22 Vi har en sirkel med radius {minSirkel.radius}. <br /> 23 Arealet av sirkelen er {minSirkel.areal}. <br /> 24 Omkretsen av sirkelen er {minSirkel.omkrets}. 25 </div>

Kalle på metoder
En metode er en funksjon som er bygd inn i et objekt. Vi kaller på en metode ved å skrive objektnavn.metodenavn(argumenter). Vi setter punktum etter objektnavnet for å hente ut metoden, akkurat som når vi henter ut verdien fra et felt. Når vi kaller på metoden må vi alltid ha med parentesene til slutt, slik som med funksjoner. Eventuelle argumenter skriver vi mellom parentesene.
Metodenavn bør starte med et verb. Feltnavn bør starte med et substantiv. Det er fordi metoder gjør noe, mens felter er noe. Dette hjelper leserne med å se forskjellen på metoder og felter når de leser koden. Er du flink, klarer du å gi et så godt navn til metodene og feltene at leserne ikke trenger å lese mer enn metode- eller feltnavnet og eventuelle parameternavn når de skal bruke klassen.
BRUKE METODER TIL Å GJØRE UTREGNINGER MED DATAENE I OBJEKTET
Metoder kan ikke bare endre innholdet i felter. De kan også brukes til å gjøre utregninger, slik at vi muligens kan klare oss med færre felter. Når vi snakker om utregninger, viser vi ikke bare til matematiske utregninger, men også til alt mulig annet som vi kan gjøre med frittstående funksjoner, som å sette sammen strenger.
I Sirkel kan vi for eksempel regne ut arealet og omkretsen når vi trenger dem, i stedet for å lagre verdiene i felter. Da slipper vi alt ekstraarbeidet med å sørge for at de tre feltene stemmer overens, og vi trenger bare å lagre den verdien som bestemmer de to andre: radius. I kodesnutten nedenfor har vi gjort nettopp dette. I stedet for å regne ut areal og omkrets i konstruktøren har vi laget to metoder som regner ut arealet og omkretsen når de trengs: regnUtAreal og regnUtOmkrets. Vi har kvittet oss med settOmkrets, for nå er det ikke lenger behov for noe ekstraarbeid når vi endrer radiusen, så i stedet kan vi bare endre den direkte.
For moro skyld har vi lagt til et input-felt som er bundet sammen med verdien til feltet radius i minSirkel. Når vi redigerer verdien i feltet, oppdateres det utregnede arealet og den utregnede omkretsen automatisk på nettsiden.
1 <!-- sirkel-med-automatisk-utregning.svelte --> 2 <script> 3 class Sirkel {
4 constructor(radius) {
5 this.radius = radius
6
7 8 9 regnUtAreal() { return Math.PI * this.radius * this.radius
10 11 12 13 14 15 } 16 regnUtOmkrets() { return 2 * Math.PI * this.radius
17 let minSirkel = new Sirkel(10) 18 </script>
19
20 <div> 21 Vi har en sirkel med radius {minSirkel.radius}. <br /> 22 Arealet av sirkelen er {minSirkel.regnUtAreal()}. <br /> 23 Omkretsen av sirkelen er {minSirkel.regnUtOmkrets()}. 24 </div> 25 <div> 26 <label>
27 Radius:
28 <input type="number" bind:value={minSirkel.radius} />
29 </label> 30 </div>

Vi kunne ha laget frittstående funksjoner som gjorde den samme jobben som meto-
dene vi akkurat la til, altså let regnUtAreal = (radius) => { ... } og let regnUtOmkrets = (radius) => { ... }. Fordelen med en metode er at vi ofte kan kutte ut én eller flere parametere som vi hadde trengt dersom den var en frittstående funksjon. Metoden kan som nevnt hente inn data fra felter ved å bruke nøkkelordet this.

EKSEMPEL
Sette sammen strenger i en metode
Nå skal vi legge til en metode i Hund, slik at alle hunder kan presentere seg på en nettside. Vi kaller metoden for formulerPresentasjon.
1 // _hund.js 2 export class Hund { 3 constructor(navn) { 4 this.navn = navn
5 } 6 7 formulerPresentasjon() { 8 return `Jeg heter ${this.navn}. Voff!`
9 } 10 }
I formulerPresentasjon har vi brukt en malstreng (template string på engelsk) for å sette sammen en streng. Du finner en nærmere forklaring på hva en malstreng er, i faktaboksen nedenfor. Vi kaller på metoden mellom krøllparentesene i linje 9 for å vise resultatet. Vi har lagt til et input-felt der vi kan endre navnet på hunden, slik at vi kan se at metoden kjøres automatisk idet vi endrer hundens navn.
1 <!-- bundet-hund-med-metode.svelte --> 2 <script> 3 import { Hund } from "./_hund.js"
4
5 let hundeobjekt = new Hund("Fido") 6 </script>
7 8 <h1>Hei, hund!</h1> 9 <div>{hundeobjekt.formulerPresentasjon()}</div> 10 <label>Hundenavn: <input bind:value={hundeobjekt.navn} /></label>

Malstrenger
En malstreng har gravistegn``(backticks på engelsk) i starten og slutten i stedet for vanlige anførselstegn (" "). Hvis vi skriver ${variabelnavn} i løpet av strengen, setter vi inn variabler: `Jeg heter ${variabelnavn}. Voff!`. Dette ligner på måten vi kan sette inn variabler på i HTML-delen av en Svelte-fil. Det er bare i script-delen av koden og i JavaScript-filer vi må bruke dollartegnet foran krøllparentesen.
Vi kan oppnå samme resultat dersom vi slår sammen strenger med plusstegnet: "Jeg heter " + this.navn + ". Voff!". I JavaScript foretrekker vi å bruke malstrenger foran plusstegnet fordi de krever mindre plass i kildekoden, og fordi det er lettere å se om vi har husket mellomrommene som skal med.
EKSEMPEL
Metoder som kaller på andre metoder
Metoder kan kalle på andre metoder i klassen i tillegg til å hente ut verdier fra felter.
Nedenfor definerer vi klassen Kontakt, som brukes til å lagre virtuelle visittkort, altså kort med kontaktopplysningene til en person og navnet på firmaet der personen jobber. I tillegg til konstruktøren definerer klassen to metoder: settSammenFulltNavn, som returnerer en streng med personens fulle navn, og lagVisittkort, som formaterer det som skal stå på visittkortet. Fordi personens fulle navn skal stå på visittkortet, kaller
lagVisittKort på metoden settSammenFulltNavn for å gjøre jobben sin.
1 // _kontakt.js 2 export class Kontakt { 3 constructor(fornavn, etternavn, epostadresse ) { 4 this.fornavn = fornavn 5 this.etternavn = etternavn 6 this.epostadresse = epostadresse
7 } 8 9 settSammenFulltNavn() { 10 return `${this.fornavn} ${this.etternavn}`
11 } 12
13 lagVisittkort() { 14 return `${this.settSammenFulltNavn()}, e-post: ${this.epostadresse}`
15 } 16 }
1 <!-- kontaktliste.svelte --> 2 <script> 3 import { Kontakt } from "./_kontakt.js"
4
5 let kontakter = [
6 /*
7 8 9 10 11
12 ] 13 </script>
14 * Vi oppretter kontaktene rett i arrayet, * uten å legge dem i en variabel. */ new Kontakt("Ole", "Olsen", "ole.olsen@example.no"), new Kontakt("Kari", "Nordmann", "kari.nordmann@ example.no"),
15 <h1>Mine kontakter</h1> 16 <ul> 17 {#each kontakter as kontakt} 18 <li>{kontakt.lagVisittkort()}</li> 19 {/each} 20 </ul>
Et typisk visittkort inneholder kontaktopplysningene til en person og navnet på firmaet der personen jobber.

Kontaktopplysningene til to personer vises i en punktliste.

NØKKELORDET THIS
Som vi har sett, bruker vi ofte nøkkelordet this når vi definerer en klasse – både når vi oppretter felter og metoder, og når vi refererer til dem inne i en klassedefinisjon. Nøkkelordet this brukes fordi et objekt trenger en måte å vise til seg selv på, enda det ikke vet hvilket variabelnavn det kommer til å få i fremtiden. En metode vet ikke engang om objektet den hører til, skal få et navn i det hele tatt. For eksempel fikk ikke kontaktene i kontaktlisten i forrige eksempel egne variabelnavn.
Objekter bruker nøkkelordet this for å vise til seg selv i metoder, slik at metodene kan hente ut data fra felter, endre dataene i felter og kalle på andre metoder i samme objekt. For å hente ut verdien fra et felt i en metode skriver vi this.feltnavn. For å gi en ny verdi til et felt i en metode skriver vi this.feltnavn = nyVerdi. For å kalle på en metode skriver vi this.metodenavn(argumenter).
TVINGE FREM OPPDATERINGER ETTER METODEKALL
Hittil har vi sett mange eksempler på at innholdet på nettsiden oppdateres automatisk hvis vi gir et felt en ny verdi, enten ved å skrive objektnavn.feltnavn = nyVerdi eller ved å binde sammen input-felter og feltnavn ved å skrive bind:value={objektnavn.feltnavn}. Hvis vi bruker metoder for å oppdatere innholdet i objektet, kan det oppstå situasjoner der de oppdaterte verdiene ikke vises på skjermen. Som vi skal se i det følgende eksemplet, er dette noe som heldigvis lar seg løse.
Vi har en klasse kalt Teller som er lagret i filen _teller.js. Denne klassen representerer en slik telledings som dørvakter på for eksempel fotballstadioner og utesteder har i hånda: Trykker du på knappen, øker verdien med én.
1 // _teller.js 2 export class Teller { 3 constructor() { 4 this.antall = 0
5 } 6 7 tell() { 8 this.antall = this.antall + 1
9 } 10 11 beskrivTelling() { 12 return `Du har talt ${this.antall}`
13 } 14 }
tell-metoden kalles på, men visningen oppdateres ikke.
Nå oppdateres visningen når vi trykker på knappen. Vi skal lage en nettside som tester denne telleren. Du får se første-, andre- og tredjeutkastet til nettsiden, der andreutkastet inneholder en programfeil som vi fikser i tredjeutkastet. Nedenfor ser du førsteutkastet.
1 <!-- teller-test.svelte --> 2 <script> 3 import { Teller } from "./_teller.js"
4 5 let minTeller = new Teller() 6 </script>
7 8 <h1>Teller</h1> 9 <p>{minTeller.beskrivTelling()}</p> 10 <button on:click={() => (minTeller.antall = minTeller.antall + 1)}> 11 Tell 1 til 12 </button>
Her viser vi frem resultatet av å kalle på beskrivTelling. Vi har også en knapp som øker verdien i feltet minTeller.antall med én når vi klikker på den, ved å skrive rett til feltet. Etter at vi har klikket på knappen seks ganger, står det at vi har talt til 6. Altså fungerer programmet som forventet. Da vi skrev førsteutkastet, overså vi metoden tell. Fordi vi har lyst til å ta imot all hjelpen som klassen tilbyr oss (og teste at alle metodene virker), prøver vi å bruke metoden tell i stedet for å oppdatere feltet selv. Vi endrer slik at knappen kaller på metoden tell.
1 <button on:click={() => minTeller.tell()}>Tell 1 til</button>
Når vi nå tester koden, hender det noe rart: Antallet ser ikke ut til å øke! Det skjer i alle fall ikke noe der vi kaller på minTeller.beskrivTelling(). Hvis vi legger til
koden console.log("Antall: " + minTeller.antall) i tell-metoden, klikker fire ganger på knappen og åpner utviklerkonsollen i nettleseren, ser vi derimot at det har blitt skrevet ting til konsollen hver gang vi klikket på knappen.
Av disse meldingene kan vi lese at metoden har blitt kalt på. Hvorfor oppdateres da ikke visningen? For å få Svelte til å reagere på at vi har endret innholdet i et objekt med en metode, må vi tilegne objektet til seg selv i linjen etter at vi har kalt på meto-
den, ved å skrive: minTeller = minTeller.
Nedenfor ser du hvordan vi til slutt endrer knappen i teller-test.svelte slik at visningen oppdateres. Dette må vi gjøre selv om vi bruker metoden tell for å oppdatere innholdet i
minTeller.
1 <button 2 on:click={() => {
3
4
5 minTeller.tell() // Sender signal om å oppdatere visningen. minTeller = minTeller
6 }}
7 > 8 Tell 1 til 9 </button>
Når en variabel står mellom krøllparenteser i HTML-delen av dokumentet i Svelte, for eksempel <div>{variabelnavn}</div>, styrer variabelen denne delen av visningen. Svelte oppdaterer dette området automatisk når variabelen variabelnavn står på venstre side av et likhetstegn i en kodelinje den kjører, for eksempel varia-
belnavn = nyVerdi eller variabelnavn.feltnavn = nyVerdi. Svelte oppdaterer også visningen automatisk dersom variabelen er bundet sammen med et HTML-attributt ved hjelp av bind:.
Når vi oppdaterer innholdet i et objekt som styrer visningen, med en metode, for
eksempel objektnavn.metodenavn(), må vi skrive objektnavn = objektnavn i den påfølgende linjen for at Svelte skal forstå at det har skjedd en oppdatering.
EKSTRASTOFF
Svelte er nødt til å overse metoder for å unngå uendelige løkker
Linjen minTeller = minTeller trengs på grunn av måten Svelte holder visningen oppdatert på. Som du sikkert vet, betyr krøllparenteser i HTML-koden, slik som
{minTeller.beskrivTelling()} eller bare {enHvilkenSomHelstVerdi}, at vi setter inn verdien i dokumentet. Svelte setter ikke bare inn verdien når vi skriver krøllparenteser. Når Svelte vet at variabelen som krøllparentesen starter med (altså minTeller og enHvilkenSomHelstVerdi), har fått en ny verdi, oppdaterer Svelte også det som står mellom krøllparentesene. Problemet vi støter på her, er at Svelte ikke kan vite når det kommer til en linje med et metodekall dersom objektet som metoden kalles på, endrer seg. Svelte vet altså ikke om minTeller har fått nytt innhold i feltene når vi kaller på minTeller.tell(). Svelte reagerer på endringer i variabler hvis og bare hvis • variabelen står på venstre side av et enkelt likhetstegn i en kodelinje som kjøres • variabelen får ny verdi fordi den er bundet med bind: Førsteutkastet fungerte altså fordi kodelinjen minTeller.antall = minTeller.antall + 1 kjøres når vi klikker på knappen, og minTeller står på venstre side av et likhetstegn. I andreutkastet var det verken likhetstegn eller binding, og derfor visste ikke Svelte at minTeller hadde fått en ny verdi. I tredjeutkastet satte vi inn minTeller = minTeller for å fortelle Svelte at «nå vet jeg, programmereren, at minTeller har fått ny verdi», og derfor virket programmet som forventet igjen. Av ulike grunner er det ikke praktisk mulig for en datamaskin å analysere et stykke kode (for eksempel en metode) og ha full oversikt over konsekvensene av å kjøre den. Dermed er det ikke mulig for Svelte å analysere en metode for å finne ut om den endrer noe eller ikke. Den er nødt til å holde seg til overfladiske, sikre tegn, som = og bind:. Men hvorfor er det så viktig å vite om det skjer endringer eller ikke? Kan ikke Svelte bare oppdatere, uavhengig av om det skjer en endring eller ikke? Vi bruker ganske ofte metoder når vi viser frem data, slik som vi prøvde med {minTeller.beskrivTelling()}. Slike metodekall hadde imidlertid forårsaket en uendelig løkke som hadde endt opp med å krasje nettsiden dersom siden skulle ha blitt oppdatert for hvert metodekall. Da hadde vi fått et metodekall for å finne verdien idet siden lastes inn, fulgt av en oppdatering av nettsiden fordi det ble kalt på metoden, fulgt av et metodekall for å finne verdien som skal oppdateres, fulgt av en oppdatering, og så videre. Derfor kan ikke Svelte oppdatere idet vi kaller på metoder.