Funktiot ovat olioiden ohella keskeisin osa JavaScript-kieltä. Itse asiassa myös funktiot ovat olioita, joilla on joitakin erikoisominaisuuksia. Funktioina ohjelmoidaan aliohjelmat, proseduurit, "oikeat" arvon palauttavat funktiot ja ns. sulkeumat, ym., ym. Niillä voidaan simuloida tavanomaisten ohjelmointikielten rakenteita: moduleita, kapseloiden suojattuja tietokenttiä, olion metodeita, aksessoreita, staattisia muuttujia (a'la Javan static). JavaScriptin funktoilla voidaan ohjelmoida sujuvasti myös ankaran funktionaalisen paradigman tyyliin. Kaiken muun lisäksi funktioilla toteutetaan myös olioiden luonti, "konstruointi", mutta siitä vasta seuraavassa luvussa...
function summa(a,b) {
var tulos = a+b;
return tulos;
}
function muutaMolemmat (taulu1,taulu2) {
taulu1[1] = "kissa"; // taulukko-olion alkio on muutettavissa
taulu2 = [666,666,666]; // sijoitus muodolliseen parametriin ei muuta todellista
}
var t1 = [1,2,3]
var t2 = [1,2,3]
muutaMolemmat(t1,t2);
write(t1); // 1,kissa,3
write(t2); // 1,2,3
var sum = function(a,b) {return a+b} // funktioliteraali muuttujan arvoksi
write(sum(1,2));
toinensumma = sum; // kopioidaan viite funktio-olioon
write(toinensumma(3,4));
var sum = (a,b) => a+b write(sum(1,2)); toinensumma = sum; write(toinensumma(3,4));
Jos muodollisia parametrejä on vain yksi, edes sulkeita ei tarvita:
var triplaa = a => a*3 write(triplaa(7));
function sum(a,b) {return a+b}
toinensumma = sum;
write(toinensumma(3,4));
function sarjanSumma(termi, raja) {
var summa = 0;
for (var i=1; i<=raja; ++i)
summa += termi(i);
return summa;
}
aritmeettinen = sarjanSumma(i => i, 10);
write(aritmeettinen); // 55
harmoninen = sarjanSumma(i => 1/i, 10);
write(harmoninen); // 2.9289682539682538
Toki termifunktiot voi nimetäkin, mutta tämä on ehkä turhaa, jos
noita funktioita käytetään vain kerran:
function sarjanSumma(termi, raja) {
var summa = 0;
for (var i=1; i<=raja; ++i)
summa += termi(i);
return summa;
}
function aTermi(i) {return i};
aritmeettinen = sarjanSumma(aTermi, 10);
write(aritmeettinen);
function hTermi(i) {return 1/i};
harmoninen = sarjanSumma(hTermi, 10);
write(harmoninen);
function map(muunnos,taulukko) {
var tulos = []; // uusi tyhjä taulukko
for (var i = 0; i < taulukko.length; i++)
tulos[i] = muunnos(taulukko[i]);
return tulos;
}
var luvut = [1,2,3,4,5]
var a = map(x => x*x, luvut);
write(a); // 1,4,9,16,25
write(map(x => x+x, luvut)); // 2,4,6,8,10
write(map(function(x) {return 1/x}, luvut)); // 1,0.5,0.3333333333333333,0.25,0.2
write(map(x => " luku"+x, luvut)); // luku1, luku2, luku3, luku4, luku5
var factorial = function fac(n) {return n<2 ? 1 : n * fac(n-1)};
write(factorial(5)); // 120
Tässä tunnuksen fac näkyvyysalue on vain kyseinen funktioliteraali!
Vertaa tätä nimettyyn funktioon fac:
function fac(n) {return n<2 ? 1 : n * fac(n-1)}
write(fac(5)); // 120
var factorial = function(n) {return n<2 ? 1 : n * factorial(n-1)};
// viitataan funktioarvoiseen muuttujaan
write(factorial(5)); // 120
var munOmaKertoma = factorial; // kopioidaan funktioviite muuttujaan munOmaKertoma
write(munOmaKertoma(5)); // 120 ja kaikki toistaiseksi vielä hyvin
factorial = function() {return 6}; // uusi funktioarvo muuttujalle factorial
write(munOmaKertoma(5)); // 30 == 5*6 eli hassusti käy!
Ongelma on siis siinä, että munOmaKertoma-funktion
toiminta muuttuu ohjelmoijan sitä havaitsematta.
var factorial = function(n) {return n<2 ? 1 : n * arguments.callee(n-1)}
write(factorial(5)); // 120
Tämä ei ole sallittua strict-moodissa. Suositeltavampaa on käyttää
paikallista nimeä.
Kurkistus "konepellin alle": Asia on kuitenkin kiinnostava! Mikä on tuo arguments? Muokataan ohjelmaa ja katsotaan Pienellä testiympäristöllä, millaisia kenttiä arguments-oliolla on.
var factorial = function(n, turha) {
oK(arguments)
return n<2 ? 1 : n * arguments.callee(n-1)
}
factorial(1, 365)
Saadaan tulos:
0: 1
1: 365
length: 2
callee: function(n, turha) {
oK(arguments)
return n<2 ? 1 : n * arguments.callee(n-1)
}
--------
Ja sieltähän se callee löytyi!
[Suoritusaikainen arguments-olio sisältää todellisten parametrien arvot indeksoituina nollasta alkaen, niiden lukumäärän (length-kenttä) ja viitteen itse funktioon (callee-kenttä)]
var summa = new Function('a', 'b', 'return a + b')
write(summa(2, 6)) // 8
Tässä siis konstruoidaan uusi funktio antamalla
Function-olioiden
konstruktorifunktiolle kolme merkkijonoparametria!
var a = prompt("Anna a:n arvo");
var b = prompt("Anna b:n arvo");
var mitamita = prompt("Ja mitäs niillä tehdään?");
var f = new Function('a', 'b', 'return '+mitamita);
write("a=" + a + ", b=" + b)
write(mitamita + " = " + f(parseInt(a),parseInt(b)))
function ulompi() {
// koodia...
sisempi();
// koodia...
function sisempi() {write("Minä löydyn!");}
}
ulompi(); // Minä löydyn!
Normaalisti toki on selkeintä kirjoittaa alifunktioiden määrittelyt funktion alkuun, mutta jos alifunktioilla toteutetaan keskinäistä rekursiota ("mutual recursion"), kieleen valittu ratkaisu on hyvä.
Esimerkki keskinäisestä rekursiosta (joku vähän varttuneempi saattaa tunnistaa tämän vanhoilta peruskursseilta... ;-):
function taikuutta(raja) {
function abra(jono) {
if (jono.length < raja) {
jono += "ABRA"
write("Abra ennen kadin kutsua : "+jono)
kad (jono) // kad siis tunnetaan jo täällä!
write("Abra jälkeen kadin kutsun: "+jono)
}
}
function kad(jono) {
if (jono.length < raja) {
jono += "KAD"
write("Kad ennen abran kutsua : "+jono)
abra (jono)
write("Kad jälkeen abran kutsun : "+jono)
}
}
abra("")
}
taikuutta(15)
Tulostus:
Abra ennen kadin kutsua : ABRA Kad ennen abran kutsua : ABRAKAD Abra ennen kadin kutsua : ABRAKADABRA Kad ennen abran kutsua : ABRAKADABRAKAD Abra ennen kadin kutsua : ABRAKADABRAKADABRA Abra jälkeen kadin kutsun: ABRAKADABRAKADABRA Kad jälkeen abran kutsun : ABRAKADABRAKAD Abra jälkeen kadin kutsun: ABRAKADABRA Kad jälkeen abran kutsun : ABRAKAD Abra jälkeen kadin kutsun: ABRA
function fun(a,b) {
write("a="+a +", b="+b)
}
fun(); // a=undefined, b=undefined
fun(1); // a=1, b=undefined
fun(1,2,3,4); // a=1, b=2
function fun(a,b) {
for (var i=0; i < arguments.length; ++i)
write("parametri "+ i + " on " + arguments[i])
}
write("fun():"); // fun():
fun();
write("fun(11):"); // fun(11):
fun(11); // parametri 0 on 11
write("fun(11,22,33,44):"); // fun(1,2,3,4):
fun(11,22,33,44); // parametri 0 on 11
// parametri 1 on 22
// parametri 2 on 33
// parametri 3 on 44
function f (x, y = 7, z = 42) { return x + y + z }
write(f(1)) // 50
Ennen tämä piti ohjelmoida tutkimalla onko jokin parametri
arvoltaan undefined ja tarvittaessa sijoittaa
alkuarvo funktion algoritmissa.
function kurssi(nimi, ...osallistujat) {
write("Kurssin " + nimi + " osallistujat")
for (oppilas of osallistujat)
write(oppilas)
}
kurssi("JavaScript", "Matti", "Maija")
kurssi("XYZ", "aaa","bee","cee","dee", "äf")
Tulostus:
Kurssin JavaScript osallistujat Matti Maija Kurssin XYZ osallistujat aaa bee cee dee äfKuten edellä nähtiin, vanhastaan tämä jouduttiin ohjelmoimaan tutkimalla arguments-oliota "omin käsin".
function kirjoitaKaikki(...kaikki) {
for (let yksi of kaikki)
write(yksi)
}
var params = ["kissantassu", true, 7]
var other = [1, 2, ...params]
write(other)
kirjoitaKaikki(3.14, 210, ...params, 666)
var str = "böö, säikähditkö"
var chars = [...str] // merkkijono jaetaan merkeiksi!
write(chars) // b,ö,ö,,, ,s,ä,i,k,ä,h,d,i,t,k,ö
Tulostus kokonaisuudessaan:
1,2,kissantassu,true,7 3.14 210 kissantassu true 7 666 b,ö,ö,,, ,s,ä,i,k,ä,h,d,i,t,k,ö
function f() {
var a=1, b=2;
function ff() {
var a = 11; // peittää ympäröivän funktion a:n
write(a+" "+b); // 11 2 // ja tämä koskee siis myös var-muuttujia
}
ff();
write(a+" "+b); // 1 2
}
f();
var a=10, b=11, c=12; // globaaleja
function ulompi() {
var a=2; // peittää globaalin a:n
function sisempi1() {
var b=3; // peittää globaalin b:n
function sisempiSisempi() {
var b=4; // peittää ympäröivän funktion b:n
write("sisempiSisempi: "+a+" "+b+" "+c);
} // ---- end of sisempiSisempi ---------
sisempiSisempi();
write("sisempi1: "+a+" "+b+" "+c);
} // ---- end of sisempi1 ---------
function sisempi2() {
var a=33; // peittää ympäröivän funktion a:n
write("sisempi2: "+a+" "+b+" "+c);
} // ---- end of sisempi2 ---------
sisempi1();
sisempi2();
} // ---- end of ulompi ----------
ulompi();
write("globaali: "+a+" "+b+" "+c);
sisempiSisempi: 2 4 12 sisempi1: 2 3 12 sisempi2: 33 11 12 globaali: 10 11 12
function f(x) {
var a=1, b=2;
var g = function(y) {
var c=3;
return a+b+x+ // vapaita muuttujia
y+c; // sidottuja muuttujia
}
return g(4);
}
write(f(5)); // 15
Tässä esimerkissä funktioliteraalissa määritellään muuttujien tunnukset y ja c. Nämä muuttujat on sidottu funktioliteraaliin ja niillä on merkitys vain literaalin sisällä. Vapaiden muuttujien tunnukset x, a ja b puolestaan on määritelty funktioliteraalin ulkopuolella Näkyvyyssäännöt sallivat näiden muuttujien käytön koska "sisältä näkyy ulos".
function map(muunnos,taulukko) {
var tulos = []; // uusi tyhjä taulukko
for (var i = 0; i < taulukko.length; i++)
tulos[i] = muunnos(taulukko[i]);
return tulos;
}
var luvut = [1,2,3,4,5]
var a = map(x => x*x, luvut);
// vain sidottu muuttuja
Tässä map-funktiolle siis annetaan parametrina sulkeuma, joka ei oikeastaan "sulje" yhtään mitään. (Vrt. "Joukko se tyhjäkin joukko on" ;-)
var taulu =[43,-2,9, 66, 18]; write(taulu); // 43,-2,9,66,18 taulu.sort((a,b) => a-b); write(taulu); // -2,9,18,43,66(Järjestysperiaate: funktion arvo pos. --> a > b, jne.)
function f(x) {
var a=111, b=222;
x(); // täällä parametrina saadun sulkeuman suoritus käynnistetään
a=1234; b=5678
}
function g() {
var a=10, b=20;
f(function() {a=77; b=99;}); // funktioparametri, jossa vapaita muuttujia
write(a+" "+b);
}
g(); // tulostus 77 99
Funktiolle f kirjoitetaan todelliseksi parametriksi funktioliteraali
function() {a=77; b=99;}, jossa on vapaat muutujat
a ja b. Tästä syntyy suoritusaikana sulkeuma,
jonka funktio f suorittaa.
Vaikka g-funktion paikalliset muuttujat a ja b
eivät näy f:n näkyvyysaluessa,
nimenomaan g:n muuttujien arvo käydään siis muuttamassa.
function omaFor(operaatio, kertaa) {
for (var i = 0; i< kertaa; i++)
operaatio();
}
function hoiteleHommat() {
var t = [1,2,3,4,5];
var i=0;
omaFor(function() {t[i]*=t[i];++i;}, t.length);
write(t); // 1,4,9,16,25
}
hoiteleHommat()
omaFor(() => {t[i]*=t[i];++i;}, t.length);
function teeLaskuri() {
var x=0;
return function() {++x; return x;} // palautusarvo on siis funktio!
}
var laskuri1 = teeLaskuri(); // huom: EI teeLaskuri ilman sulkeita, koska
// halutaan paluuarvo ei funktioviitettä
write("laskuri1 = " + laskuri1()); // 1
write("laskuri1 = " + laskuri1()); // 2
write("laskuri1 = " + laskuri1()); // 3
var laskuri2 = teeLaskuri();
write("laskuri2 = " + laskuri2()); // 1 !
write("laskuri2 = " + laskuri2()); // 2
write("laskuri1 = " + laskuri1()); // 4 !
var laskuri3 = teeLaskuri();
write("laskuri3 = " + laskuri3()); // 1
Kuten nähdään, kukin laskuri aloittaa laskennan ykkösestä. Jokaisella sulkeumalla siis on oma x-muuttujansa. Tuo muuttuja on myös piilossa, siihen ei pääse käsiksi. (Syttyykö mikään lamppu? ;-)
function piste() {
var x=0, y=0; // piilossa pidetyt kentät
// aksessorit:
return { // palautetaan siis olio, joka
getX: function() {return x}, // sisältää aksessorit!
setX: function(ux) {
ux = parseFloat(ux);
if (!isNaN(ux)) // vain numeeriset kelpaavat
x=ux;
},
getY: function() {return y},
setY: function(uy) {
uy = parseFloat(uy);
if (!isNaN(uy)) // vain numeeriset kelpaavat
y=uy;
},
print: function() {write("("+x+", "+y+")")}
}
}
var a = piste();
write(a.getX()); // 0
a.setX(87);
write(a.getX()); // 87
a.setY(3.14);
write(a.getY()); // 3.14
a.print(); // (87, 3.14)
var b = piste();
b.setX(123); b.setY(-9.762);
b.print(); // (123, -9.762)
// jatketaan edellisen ohjelman suoritusta vaihtamalla b:n kentän sisältö:
b.print = function() {write("BÖÖ!")}; // mikään ei estä tätä!
b.print(); // tulostuu BÖÖ!
var taulu =[43,-2,9, 66, 18]; write(taulu); // 43,-2,9,66,18 taulu.sort((a,b) => a-b); write(taulu); // -2,9,18,43,66
function f(x) {
var a=111, b=222;
x(); // täällä parametrina saadun sulkeuman suoritus käynnistetään
}
function g() {
var a=10, b=20;
f(() => {a=77; b=99}); // funktioparametri, jossa vapaita muuttujia
write(a+" "+b);
}
g(); // tulostus 77 99
function teeLaskuri() {
var x=0;
return () => ++x // !!
}
var laskuri1 = teeLaskuri()
write("laskuri1 = " + laskuri1()); // 1
write("laskuri1 = " + laskuri1()); // 2
write("laskuri1 = " + laskuri1()); // 3
var laskuri2 = teeLaskuri();
write("laskuri2 = " + laskuri2()); // 1 !
write(eval(23)); // 23
write(eval("23")); // 23
write(eval("23")+44); // 67
write(eval("23")+"44"); // 2344
eval("var a=123");
write(a); // 123
var b=987;
eval(b=456);
write(b); // 456
// ...
// ... on jopa mahdollista:
eval(prompt("Anna ohjelmarivi niin suoritan sen"))
true: 5, -3.14, 0, "", '', " ", "123", false, true, -0, ... false: NaN, Infinity, -Infinity, "abc", ...
true: NaN, "abc", false: 23, "23", Infinity, -Infinity, "", '', " ", false, true, -0, ...
parseInt("123") --> 123
parseInt("123abc") --> 123
parseInt(123) --> 123
parseInt(123asd) --> SyntaxError: identifier starts immediately after numeric literal
parseInt("kissa") --> NaN
parseInt(true) --> NaN
parseInt("") --> NaN
parseInt(Infinity) --> NaN
var a = { valueOf: function() {return 123},
toString: function() {return "abc"}
}
write(Number(a)); // 123
write(String(a)); // abc
a = Date();
write(Number(a)); // NaN
write(String(a)); // Mon Nov 12 2018 16:14:04 GMT+0200 (Eastern European Standard Time)
//myös toki:
write(a) // Mon Nov 12 2018 16:14:04 GMT+0200 (Eastern European Standard Time)
Siispä toteuttamalla omaan olioon metodit valueOf ja
toString voi määritellä olion
"arvon" ja "merkkiesityksen".
function sum(a,b) {return a+b}
var plus_7 = sum.bind(null, 7);
write(plus_7(9)) // 16
var arpaLisays = sum.bind(null, Math.floor(10*Math.random()))
write(arpaLisays(10)) // esim. 14
function f(a,b,c,d) {
write(a + "/" + b + "/" + c + "/" + d)
}
var f0 = f.bind(null) // ei kiinitetä yhtään parametria
f0(1,2,3,4) // 1/2/3/4
var f1 = f.bind(null,10) // kiinitetään a
f1(1,2,3) // 10/1/2/3
var f2 = f.bind(null,10,20) // kiinitetään a ja b
f2(1,2) // 10/20/1/2
var f3 = f.bind(null,10,20,30) // kiinitetään a, b ja c
f3(1) // 10/20/30/1
var f4 = f.bind(null,10,20,30,40) // kiinnitetään kaikki parametrit
f4() // 10/20/30/40
function lounas(nimi,herkku,lkm) {
//jotain raskasta laskentaa ...
write(nimi + "lle " + lkm+" "+ herkku + "a!")
}
// perusversio:
lounas("Sini", "piirakka", 3) // Sinille 3 piirakkaa!
// erikoistetaan:
var sininLounas = lounas.bind(null, "Sini", "piirakka")
sininLounas(7) // Sinille 7 piirakkaa!
sininLounas(2) // Sinille 2 piirakkaa!