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.9289682539682538Toki 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)); // 120Tä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)); // 120Tä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)) // 8Tä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)) // 50Ennen 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 99Funktiolle 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!