Functies in JavaScript
1. Wat is een functie?
2. Hoe is een functie opgebouwd?
3. Wanneer gebruik je functies?
4. Hoe gebruik je een functie?
5. Call by reference en call by value
6. Recursieve functies
7. JavaScript is clientside
8. Voorbeelden
9. Veel voorkomende fouten
10. Tips
1. Wat is een functie?
Een functie is een stuk code dat een bepaalde bewerking (of een reeks van
bewerkingen) uitvoert.
top
2. Hoe is een functie opgebouwd?
Een functie ziet er (abstract) als volgt uit:
function
<f_naam>([<f_parameter1>[, <f_parameter2>, [...]]]) {
// pre: <f_pre>
// post: <f_post> of
ret: <f_ret>
[var <f_lokale_var1>; [var
<f_lokale_var2>; [...]]]
<f_body>
[return <f_returnwaarde>;]
} |
Uitleg:
<...> Een naam die (meestal) een variabele voorstelt.
<f_naam> De naam die je aan de functie geeft. Het
is verstandig om de functie een naam te geven die omschrijft wat de functie
doet.
[...] Variabelen die tussen rechte haken staan
zijn optioneel - deze hoeven niet per sé in een functie voor te komen. Een
functie hoeft dus geen parameters, lokale variabelen of return-value te hebben
(zie hieronder voor uitleg).
<f_parameter1>,
<f_parameter2>, ... Deze variabelen vormen de invoer van je
functie. Als je invoerparameters nodig hebt, omdat je bijvoorbeeld iets wilt
berekenen dan kun je hier voor elke variabele die je denkt nodig te hebben een
(parameter)naam invoeren. Deze parameters zijn een soort van dummy-variabelen -
bij de _declaratie_ van de functie gebruik je deze variabelen om aan te geven
wat er dient te gebeuren met de bijbehorende waarden binnenin de functie. Als je
een functie _aanroept_ gebruik je concrete waarden (of objectreferenties) in
plaats van parameter-namen.
Vergelijk:
// functie
declaratie:
function som(a, b) {
// pre: a en b zijn twee
positieve gehele getallen
// ret: de som van a en b
// retourneer het resultaat
return(a+b);
}
// functie aanroep:
alert(som(1,2)); |
(er
verschijnt een popup met de waarde "3" in beeld)
Globale variabelen
(variabelen die buiten functies staan gedeclareerd) zijn binnen een functie ook
'bekend'. Het is beter om bij functies hiervan GEEN gebruik te maken, omdat dit
verwarrend werkt. Probeer er een gewoonte van te maken om alle variabelen die
een functie nodig heeft mee te geven aan de functie in de vorm van parameters.
Probeer ook te vermijden dat globale variabelen dezelfde naam hebben als
variabelen die binnenin een functie worden gebruikt.
Voorbeeld:
function huh1(test) {
test = test+1;
}
function huh2() {
test = test+1;
}
//
globale variabele 'test'
var test = 1;
huh1(test);
alert(test);
// levert 1
huh2();
alert(test);
// levert 2 !!!
|
Geef variabelen die je in een functie wilt gebruiken mee aan de functie
(of declareer ze binnen de functie), maar maak in dit geval géén gebruik van het
feit dat de functie de variabele 'toch wel kent'. In kleine scripts is deze
manier van werken misschien nog wel te volgen, maar wanneer je script groter
wordt, en variabelen op meerdere plaatsen van waarde veranderen wordt dit
hopeloos ingewikkeld.
// Als je twee slashes aan het
begin van de regel zet, word de rest van de regel als commentaar beschouwd. Hier
doet JavaScript dus verder niets mee. De rest van de regel kun je dus gebruiken
om je code toe te lichten.
<f_pre> en <f_post> of
<f_ret> Deze onderdelen zijn niet verplicht (het is
commentaar), maar het is een goede gewoonte om te omschrijven wat een functie
precies doet.
<f_pre> geeft aan wat er _voor_ de aanroep
van een functie moet gelden (préconditie) - hier kun je ook eventueel een
omschrijving geven van wat de parameters betekenen en wat voor waarden ze zouden
moeten bevatten.
<f_post> geeft aan wat er na afloop van
de aanroep van een functie geldt / zou moeten gelden (postconditie). Soms
word in plaats van '// post: ' '// ret: ' gebruikt
wanneer de functie een waarde retourneert. Het voordeel van het op deze
manier specificeren van functies is, dat het er niet toe doet hoe je een functie
schrijft (implementeert), als er maar aan de pre- en post-/ret-conditie is
voldaan - je zou dan de implementatie van de functie elk moment kunnen
veranderen.
var <f_lokale_var1>; var <f_lokale_var2>;
... Wanneer je tijdens het uitvoeren van een functie extra variabelen
nodig hebt voor het doen van bewerkingen op andere variabelen / parameters, kun
je deze (bij voorkeur aan het begin van de functie) declareren. Het is hier
wederom verstandig om omschrijvende namen te geven. Het gebruik en de precieze
betekenis van deze variabelen kun je weer met behulp van commentaar toelichten.
Lokaal (binnen de functie-declaratie) gedeclareerde variabelen bestaan
alleen binnen de functie. Je kan ertoe besluiten om bij de declaratie van lokale
variabelen deze meteen een initiële waarde te geven; Je hoeft dan alleen de
waarde van deze variabele te veranderen als dit nodig is - en dit scheelt
meestal wat code.
Vergelijk:
function groter_dan_2(getal)
{
// pre: getal is een geheel getal
// ret: de
functie geeft een boolean terug die aangeeft of getal groter is dan 2
var groter;
if(getal > 2) {
groter = true;
} else {
groter = false;
}
return groter;
} |
met:
function groter_dan_2(getal) {
// pre: getal is
een geheel getal
// ret: de functie geeft een boolean terug die
aangeeft of getal groter is dan 2
var groter = false;
if(getal > 2) {
groter = true;
}
return groter;
} |
Het
kan altijd nog korter (je hebt niet per sé een lokale variabele nodig):
function groter_dan_2(getal) {
// pre: getal is
een geheel getal
// ret: de functie geeft een boolean terug die
aangeeft of getal groter is dan 2
return(getal > 2);
} |
Dit is een mooi voorbeeld waarbij gebruik gemaakt wordt van
de pré- en post- (of ret-)conditie:
De in- en uitvoer blijft hetzelfde,
terwijl de functie-body continu verandert.
<f_body>
Hier kun je code neerzetten die de taken uitvoert waarvoor de functie
geschreven is. Bijvoorbeeld: tel twee getallen bij elkaar op, druk een tekst af,
sorteer een array et cetera.
return <f_returnwaarde>;
Wanneer je je functie hebt uitgevoerd, en je hebt de resultaten van de
functie opgeslagen in een of andere lokale variabele, kun je het resultaat van
de functie-aanroep 'teruggeven' met behulp van het return-statement, gevolgd
door de naam van de lokale variabele. Voor de returnwaarde kun je ook een
boolse waarde nemen. Wanneer bijvoorbeeld alle onderdelen van een functie
succesvol zijn uitgevoerd (er is aan de pré- en postconditie voldaan) kun je de
waarde 'true' teruggeven - wanneer niet alle onderdelen van de functie zijn
uitgevoerd kun je 'false' retourneren. Als een functie gekoppeld is aan een
event, bijvoorbeeld aan een onSubmit-event van een formulier, zal 'return
false;' er voor zorgen dat het formulier _niet_ gesubmit wordt:
//
functie declaratie
function check(form) {
//
pre: form is het te controleren formulier
// post:
een boolean die aangeeft of het veld 'comment' inhoud heeft
var heeft_inhoud = false;
if(form.elements['comment'].value == "") {
alert("Type wat in !");
} else {
heeft_inhoud = true;
}
return heeft_inhoud;
}
|
Bijbehorend
formulier:
<form action="somepage.htm" method="post"
onSubmit="return check(this);">
comments here: <input type="text"
name="comment" /><br />
<input type="submit" value="submit"
/>
</form>
|
Het is een goede ontwerpgewoonte je
functies zo te maken, dat deze maar één return-statement hebben (meerdere
return-statements zijn in principe mogelijk).
top
3. Wanneer gebruik je
functies?
Als je vaak dezelfde bewerking moet uitvoeren, is het meestal de moeite waard om
hiervoor een functie te introduceren. Ook wanneer je een (aantal) complexe
bewerking(en die samen een logisch geheel vormen) moet uitvoeren kun je
besluiten hiervoor een functie te maken.
Het is niet echt zinnig om voor
bestaande functionaliteit een compleet nieuwe functie te introduceren:
function drukaf(tekst) {
//pre: 'tekst'
bevat een tekst die afgedrukt dient te worden
//post: 'tekst' is
afgedrukt op het scherm
document.write(tekst);
}
|
Men moet waken voor een wildgroei van dit soort functies. Functies
dienen een zekere meerwaarde te hebben.
top
4. Hoe gebruik je een
functie?
definieren Voordat je een functie kan gebruiken (aanroepen) dien je
deze te declareren. Je vertelt wat de functie doet op de manier die hierboven
staat beschreven.
aanroepen Hierna kun je de functie
gebruiken waar je deze nodig hebt.
geretourneerde waarden
gebruiken Je kan het resultaat van een functie ook weer toekennen aan
een variabele buiten de functie. Gebruikmakend van de eerdergenoemde
som-functie:
// ...
// hiervoor staat ergens de functie som()
gedeclareerd
var resultaat = som(1,2);
// de variabele 'resultaat' bevat
nu de waarde 3. |
top
5. Call by reference en call by value
Als je globale variabelen (die zijn gedeclareerd met 'var') meegeeft aan een
functie, dan kun je van de waarden van deze variabelen gebruik maken. Je kan
echter deze variabelen _niet_ van waarde doen laten veranderen binnen deze
functie.
Voorbeeld:
var tekst = "Dit is een tekst.";
function verander_tekst(txt) {
txt = "Dit is een andere
tekst.";
}
verander_tekst(tekst);
alert(tekst);
// levert
"Dit is een tekst." |
Dit komt omdat er alleen maar naar de waarde
van de meegegeven variabele (parameter) gekeken wordt. Je kan met deze waarde
werken, maar deze niet aanpassen. Dit wordt "call by value" genoemd - elke
gewone variabele die je in JavaScript aan een functie voert, heeft de eigenschap
dat deze niet van waarde veranderd kan worden, met andere woorden: deze is
(binnen de functie) constant. Soms is een parameter gewoon een getal, boolean of
een string die niet afkomstig is van een variabele. Je kan de "variabele" 1 niet
van waarde doen veranderen (zie ook de aanroep van de eerdergenoemde
som-functie, hier word geen gebruik gemaakt van aparte (globaal gedeclareerde)
variabelen).
Wat je wel kunt doen is de aangepaste waarde retourneren en
deze dan aan de variabele toekennen waarmee de functie werd aangeroepen:
var tekst = "Dit is een tekst.";
function
verander_tekst(txt) {
txt = txt+" Aangepast.";
return txt;
}
tekst = verander_tekst(tekst);
alert(tekst);
// levert "Dit is een tekst. Aangepast."
|
JavaScript heeft ook de beschikking over objecten. Wanneer je een object
aan een functie voert, dan word er een referentie van dit object aan de functie
meegegeven. Dit heet "call by reference".
(attributen van) objecten kunnen
wel van waarde veranderen.
Voorbeeld:
function
stack_push(arr, val) {
// pre: arr is een
Array-object
// post: de waarde van val is toegekend aan de
eerste vrije positie van arr
arr[arr.length] = val;
}
// declaratie van (globaal) Array-object
var arr = new
Array("a","b","c");
stack_push(arr, "d");
alert(arr.join(";"));
// levert "a;b;c;d" |
In dit voorbeeld is dus de parameter
'arr' call-by-reference, en 'val' call-by-value.
top
6. Recursieve
functies
Sommige functies roepen zichzelf aan. Dit zijn recursieve functies. Je moet hier
heel goed op twee dingen letten: voortgang en eindiging. Voortgang wil
zeggen dat een functie iets 'moet blijven doen', maar het moet ook weer niet zo
zijn dat een functie iets _eindeloos_ moet blijven doen, hij moet een keer
eindigen. Er moet dus iets in de functie zitten / aan de functie worden
meegegeven dat bij elke functie-aanroep afneemt zodat je op een bepaald punt
kunt vaststellen dat je functie 'klaar' is. Gegevens (die je aan de functie
voert) zijn altijd 'eindig' van aard. Je zou dus met behulp van eigenschappen
van deze gegevens kunnen bepalen wanneer je klaar bent met het uitvoeren van een
(recursieve) functie. Iets waar je dus goed op moet letten bij het maken
recursieve functies, is dat je functies niet in een oneindige lus terecht komen
(vooral als je alert's gebruikt om te debuggen), anders kun je in de meeste
gevallen je niet-reagerende browser afsluiten als het mis gaat :].
Voorbeeld:
// declaratie
function printboom(tree,
depth) {
// pre: tree is een array met af te drukken items,
depth is het aantal spaties*2,
// dat voor elk array-item
wordt ingesprongen
// post: alle items van tree zijn afgedrukt
for(i in tree) {
// controleer
of het huidige item een array is
if((typeof tree[i])
== "object") {
// het betreft een array,
ga een niveau dieper
printboom(tree[i],
depth+1);
} else {
// het betreft een array-item, druk het
af
for(j=0; j < depth; j++) {
document.write(" ");
}
document.write(tree[i]+"<br />");
}
}
}
// declaratie van
boom-array
var boom = new Array("1", new Array("1.1", "1.2", new Array("1.2.1",
"1.2.2")), "2", new Array("2.1", "2.2"));
// aanroep
printboom(boom,
0); |
Levert:
1
1.1
1.2
1.2.1
1.2.2
2
2.1
2.2 |
top
7. JavaScript is
clientside
JavaScript wordt uitgevoerd op de machine die een pagina op het internet
opvraagt die JavaScript bevat. Je zou JavaScript dus ook goed kunnen gebruiken
bij het besparen van bandbreedte door functies te schrijven voor het afdrukken
van bijvoorbeeld tabel-rijen. Dit heeft trouwens alleen zin als je tabellen
behoorlijk lang zijn / kunnen worden.
Vergelijk:
<table
border="1">
<tr>
<td>band</td>
<td>album</td>
<td>kwaliteit (kbps)</td>
</tr>
<tr>
<td>Deftones</td>
<td>Adrenaline</td>
<td>160</td>
</tr>
<tr>
<td>Deftones</td>
<td>White
Pony</td>
<td>160</td>
</tr>
<tr>
<td>Dropkick Murphy's</td>
<td>Sing Loud, Sing
Proud!</td>
<td>160</td>
</tr>
</table> |
Versus:
// functie declaratie
function rij(band, album, kwaliteit) {
// drukt een rij van
de muziek-tabel af
document.write("<tr><td>"+band+"</td><td>"+album+"</td><td>"+kwaliteit+"</td></tr>");
}
<table border="1">
<tr>
<td>band</td>
<td>album</td>
<td>kwaliteit (kbps)</td>
</tr>
<script language="JavaScript">
<!--
rij("Deftones", "Adrenaline", 160);
rij("Deftones", "White
Pony", 160);
rij("Dropkick Murphy's", "Sing Loud, Sing Proud!", 160);
//-->
</script>
</table>
|
Hiernaast zou
je meer functies kunnen toevoegen waarmee je de opmaak van de inhoud regelt. De
JavaScript zou op zijn beurt weer gegenereerd kunnen worden door een serverside
scripting taal, zoals PHP. Hoe meer rijen je toevoegt, hoe meer ruimte
(bandbreedte bij versturen) het relatief gezien scheelt.
top
8. Voorbeelden
top
9. Veel voorkomende fouten
- JavaScript is case-sensitive en strict in de syntax, hierdoor zijn er veel
fouten mogelijk...
top
10. Tips
- probeer alles wat je in een functie wilt gebruiken als parameter mee te
geven - dit hoef je in principe niet te doen als de variabele globaal
gedeclareerd staat, maar dit werkt _zeer_ verwarrend
- voorzie functies van een pré- en post- (of ret-)conditie
- declareer lokale variabelen aan het begin van de functie
- gebruik commentaar in je code om duidelijk te maken wat er gebeurt (of zou
moeten gebeuren)
- schrijf je functies zo, dat je maximaal één return-statement hebt
- spring bij lussen in in je code, om het geheel leesbaar te
houden
top
|