JWT 2015 - Johdatus www-tekniikoihin
© Aulikki Hyrskykari, Antti Sand, 2015 - Kopiointi ilman lupaa kielletty.

5. JavaScript

JavaScriptin avulla www-sivuille saadaan lisättyä dynaamisia toimintoja. Web on kehittynyt alkuaikojen roolistaan dokumenttien julkaisujärjestelmänä, sovelluksia tukevaksi ohjelmointiympäristöksi. Selaimen rooli käyttäjälle lähestyy yhä enemmän sitä, mikä oli käyttöjärjestelmän rooli aiemmin.

JavaScriptin olio-oriointoituneet piirteet mahdollistavqat ohjemien joustavan rakentamisen uudelleen käytettäviä ohjelmapaloja hyödyntäen. Kieli on kehittynyt alkuaikojen tehosteiden lisäyksiin käytetyistä, aliarvostetusta kielestä varsin vakavasti otettavaksi ja voimakkaaksi ohjelmointikieleksi.

JavaScriptin avulla voidaan esimerkiksi tarkistaa lomakkeelle syötettyjä tietoja, tulostaa verkkosivulle sisältöä ja muokata verkkosivulla olevaa sisältöä.

5.1 Taustaa

JavaScript on ohjelmointikieli, jonka alunperin Netscape kehitti selaimiinsa; kieli oli käytössä Netscapen selaimessa 1995 nimellä LivesScript, mutta Netscape vaihtoi nimen varhaisessa vaiheessa (Java-kielen nousun myötä) JavaScriptiksi. Microsoft otti JavaSciptin pohjaksi omaan IE 3.0 -selaimeensa, mutta lisäsi siihen joitain omia piirteitään ja kutsuu sitä nimellä Script Adobe Flashin versiota samasta kielestä kutsutaan nimellä ActionScript.

JavaScriptin nykyisen menestyksen taustalla on ECMA, joka otti vastuun kielen standardisoinnista, jota JavaScript, JScript ja ActionScript seuraavat (ECMAScript-standardit). Viimeisin kielen määrittely on JavaScript 1.8.5 (huhtikuu 2013, perustuu EcmaScript standardiin ECMA-262 (2011). JavaScript on

Toisin kuin cgi-ohjelmat, JavaScript-koodi suoritetaan suoraan selaimessa. Tälloin sivuun saatetaan tehdä paikallisia muutoksia lataamatta koko sivua uudelleen palvelimelta. Tätä periaatetta selaimessa tapahtuvasta koodin tulkinnasta ja sen vaikutuksesta sivun hahmonnukseen kutsutaan usein "dynaamiseksi HTML:ksi". JavaScriptiin on olemassa palvelinpuolen ohjelmoinnin mahdollistavia laajennusmoduleita. Laajennettuun JavaScriptiin on esitelty olioita, joiden kautta voi esimerkiksi käsitellä palvelimella sijaitsevia tietokantoja tai tiedostoja.

Takaisin ylös

5.2. JavaScriptin perusasioita

Katsotaan ensin muutama perusasia, jotka tulee tietää, kun liittää JavaScript-koodia sivulle.

5.2.1 Sivulle liitäminen

JavaScript-ohjelma upotetaan sivulle (HTML-dokumenttiin) analogisesti CSS-sääntöjen kanssa. JavaScript-koodi voidaan kirjoittaa omaksi (*.js) tiedostokseen ja tuoda se sivulle <script>-elementin avulla

  <script type="text/javasript" src="nimi.js"> </script>

tai omana elementtinään johonkin kohtaan HTML-dokumenttia

  <script>
    ... javascript-koodi

  </script>

Seuraava JavaScriptiä sisältävä HTML-dokumentti selaimeen avattuna tuottaisi sivun, jossa lukee "Opetellaan JavaScriptiä".

<!DOCTYPE html>
<head>
  <title>Ensimmäinen javascript ohjelma</title>
</head>
<body>
  <h1>Opetellaan JavaScriptiä - 1</h1>
  <script>
    document.write("<p>Tämä kappele tuotettu JavaScriptillä</p>")
  </script></body>
</html>

Esimerkki www-sivuna

Esimerkissä tutustutaan samalla alustavasti myös JavaScriptin ja HTML-sivun keskinäiseen yhteistyöhön DOM-puun välityksellä. Jokaisesta selaimeen ladatusta HTML-dokumentista muodostetaan DOM-olio document, jonka kautta päästään käsiksi kaikkiin sivulla esiteltyihin elementteihin. Yksi document-olion metodeista on write, jonka avulla voi kirjoittaa parametrina saamansa tekstin HTML-dokumenttiin siihen kohtaan missä se esiintyy. Edellisessä esimerkissä HTML-dokumentin rungoksi tulee siis vain h1-otsikko ja yhden lauseen kappale.

Periaatteessa <script>-elementin voi sijoittaa sivulle minne tahansa elementit yleensäkin voi kirjoittaa. Näissä ensimmäisissä esimerkeissä, jossa tutustutaan JavaScriptin alkeisiin, skriptit suoritetaan suoraan elementtivirrasta sivun latautuessa. Normaalimpi tilanne on, että skriptit ovat funktioita, joita sitten kutsutaan sivulta eri tavoin. Käytäntönä on usein ollut sijoittaa skripti-funktiot dokumentin otsikko-osaan. Tämä on toisaalta luonteva sijoitupaikka, koska sriptit eivät ole varsinaista sivun sisältöä, vaan eräänlaista sivun lisäinformaatiota. Skripitien kirjoittaminen dokumentin otsikko-osaan ei kuitenkaan välttämättä ole paras ratkaisu.

Selain noutaa (erilliset *.js-tiedostot) ja tulkkaa skriptit siinä järjestyksessä kun ne HTML-dokumentissa tulevat vastaan. Sinä aikana kun skriptitiedostoa tulkataan, sivulle ei tuoteta mitään <script>-elementin jäljessä määriteltyä sivun sisältöä. Näin ollen <script>-elementti "blokkaa" sivun muun sisällön tuottamisen selainikkunaan. Tästä syystä skriptit kannattaakin sijoittaa sivun loppuun juuri ennen </body>-lopputunnusta aina kun se on mahdollista.

Kuitenkin, silloin jos skriptiä kutsutaan jo sivun latauksen yhteydessä, skriptit joutuukin sijoittamaan sivun alkuun. Jos skripteja suoritetaan elementtivirrasta suoraan sivun latautuessa, se ei luonnollisesti ole mahdollista, koska kutsuttavan funktion tulee olla esiteltynä ennen kuin sitä kutsutaan.

5.2.2 Ohjelmakoodin kirjoittamisesta

Tyhjätilamerkkien käsittely

Tulkki ohittaa ylimääräiset tyhjätilamerkit (välilyönnit, sarkainmerkit ja rivinvaihdot), joten ohjelmakoodin sisennys normaaliin tapaan koodin rakenteen selkeyttämiseksi on suositeltavaa.

Erilaiset JavaScript-kirjastot, joita saata ohjelmassasi käyttää, tulevat usein yhtenä pitkänä rivinä sisentämättömässä muodossa. Tämä johtuu siitä, että moduulien toimittajat ovat minimoineet ohjelmakoodin, jotta sen tulkkaus selaimessa olisi mahdollisimman nopeata. Koodin minimointiin on käytettävissä työkaluja, sellaisia kuten Google Closure Compiler, sen online versio, tai YUI Compressor ja sen online versio

Puolipisteiden käyttö

Vastaavasti kuin C:ssä, C++:ssa tai Javassa, lauseet päätetään normaalisti puolipisteeseen. JavaScriptissä puolipisteen voi kuitenkin jättää pois, jos lause on kirjoitettu omalle rivilleen. Hyvän ohjelmointitavan mukaista on päättää lause puolipisteeseen silloinkin.

  var alku = 1; var loppu = 2; 
  var muuttuja1 = 10;
  var muuttuja2 = 20;

Kirjoitettaessa muuttujien esittelyt samalle riville, molemmat esittelyt tulee siis päättää puolipisteellä. Puolipisteen voi jättää pois vain yksin omalla rivillään olevasta lauseesta, on kuitenkin hyvän tavan mukaista päättää lause aina puolipisteeseen.

Case-sensitiivisyys ja nimeämiskäytännöt

JavaSript on case-sensitiivinen kieli, joten tunnuksissa (kielen varatut sanat, muuttujien nimet, jne) isot kirjaimet ovat merkitseviä (Muuttuja ≠ muuttuja).

Varsinaisia pakottavia sääntöjä tunnusten nimeämiskäytännöissä ei ole, mutta "camelCase" kirjoitustapa on JavaScript-koodissa yleinen tunnusten krjoituskäytäntö (esimerkiksi under_score -käytännön sijasta). Nimeämiskäytännöistä tärkein on käyttää ohjelmissa hyvin kuvaavia nimiä ja säilyttää omaksumansa nimeämiskäytäntö samana.

Käy myös selaamassa läpi tämä hyvä lista erilaisista nimeämiskäytännöistä
Jeff Way: 9 Confusing Naming Conventions for Beginners.

Koodin kommentointi

Tulkki jättää rivin loppuun asti huomiotta kaiken tekstin, joka on kirjoitettu kahden kauttaviivan (//) ja myös kaiken tekstin joka on kirjoitettu merkkien /* ja */ väliin. Edellisellä voi siis kirjoittaa ohjelmadoodiin yhden rivin ja jälkimmäisellä useamman rivin kommentteja.

  document.write("Seuraavat rivit eivät näy sivulla")
    // yhden rivin kommentti
    /* javascript-tulkki ohittaa kaikki merkit
       oli se sitten mitä tahansa
       kunnes löydetään kommentin lopettavat merkit */
  document.write("Tämä taas näkyy")

JavaScript kohtelee HTML:n kommentin aloittavaa merkkijonoa <!-- samoin kuin //-merkkejä: loppurivi tulkitaan kommentiksi. Tästä syystä <script>-alkumerkkiä seuraava <!-- rivi jää JavaScriptiltä huomiotta ja puolestaan koko JavaScript-ohjelma HTML:ltä huomiotta vaikka <script> elementtiä ei tunnistettaisikaan. JavaScript ei tunnista HTML-kommentin lopettavia --> merkkejä: ne piilotettiin JavaScript tulkilta //-merkeillä.

Vanhoilla sivuilla saattaa nähdä että koodi on yllä esitettyyn tapaan ympäröity <!-- ja --> merkeillä. Sillä oli tarkoitus välttää virheet selaimilla, jotka eivät ymmärtäneet JavaScriptiä.

  <script language="javascript" type="text/javascript">
  <!--
    document.write("Opetellaan JavaScriptiä!")
  // -->
  </script>

Kaikki selaimet ovat kuitenkin jo kauan tukeneet JavaScriptiä, joten tämä varotoimenpide on nykyisin tarpeeton.

5.2.3 Varatut sanat

Alla olevassa taulukossa on lueteltu kielen varatut sanat, joita ei siis voi käyttää itse nimetyissä kohteissa tunnuksina:

abstract
boolean
break
byte
case
catch
char
class
const
continue
debugger
default
delete
do
double
else
enum
export
extends
false
final
finally
float
for
function
goto
if
implements
import
in
instanceof
int
interface
long
native
new
null
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
true
try
typeof
var
void
volatile
while
with




5.2.4 Syöttö ja tulostus (no native I/O)

Koska JavaScript on suunniteltu kieleksi, jota ajetaan jossain isäntäympäristössä (useimmiten selaimessa), kieleen ei ole määritelty syöttö- ja tulostuslauseita. Yksinkertaisin tapa syöttää ja tulostaa tietoa wwww-sivulla, on käyttää JavaScriptiin valmiiksi toteutettuja viestiruutuja.

Kolme yksinkertaista I/O-toimintoja toteuttavaa viestiruutua ovat: alert, confirm ja promt. Ne kaikki ovat modaalisia viestiruutuja, jotka aukeavat pop up-tyyppisesti selainikkunan päälle. Niiden poistaminen ja selaimen käyttämisen jatkaminen vaativat käyttäjältä jotain toimintaa, hiiren napsauttamista tai syötteen antamista.

alert-viestiruutu on tarkoitettu viestien ja (virhe)ilmoitusten antamiseen. Sen avulla voi tulostaa merkkijonon, ja sitä tuleekin usein käytetyksi sivujen rakennusvaiheessa skriptien jäljittämiseen. Seuraavassa esimerkissä tulostettava merkkijono on rakennettu JavaScriptin merkkijonojen konkatenaatio-operaattorilla +. img/05-js-valintaikkuna-alert.png

alert("Asiakkaan etunimi on: " + etunimi)

confirm-ikkunan avulla käyttäjälle voi esittää kysymyksen, johon käyttäjä voi vastata joko ok tai cancel. Jos vastaus on "ok", funktio palauttaa arvonaan kutsukohtaan totuusarvon true, jos taas "cancel" funktion arvona palautuu false. img/05-js-valintaikkuna-confirm.png

if (confirm("Haluatko jatkaa?")==true)
   alert("Okei, jatketaan!)
else alert("Hyvä on, lopetaan.");

prompt-ikkunan avulla käyttäjältä voidaan pyytää syötettä tekstikenttään. Valintaikkunassa on myös ok ja cancel -painikkeet. Jos käyttäjä painaa ok, funktio palauttaa syötetyn arvon, jos cancel, palautetaan null. img/05-js-valintaikkuna-prompt.png

var nimi = prompt("Kirjoita tähän nimesi:","Matti Malliesimerkki")
alert("Hyvää päivää " + nimi);

Kehittäjätyökaluissa on myös yleensä mukana konsoli, johon kirjoitettu koodi suoritetaan ja evaluoinnin tulos näytetään välittömästi. Sen avulla voi esimerkiksi helposti tarkistaa JavaScript-lausekkeiden arvoja. Konsolin koodi suoritetaan auki olevan sivun kontekstissa, joten esimerkiksi koodi document.location.href palauttaa auki olevan sivun url-osoitteen.

Konsolille voi myös tulostaa sivulla suoritettavasta skriptistä esim. käyttäen funktioita console.log(lauseke) tai console.debug(lauseke). Ensimmäinen näistä tulostaa arvon konsolille, jälkimmäisessä mukana linkki ohjelman riville, funktion kutsukohtaan. Console-funktiot vaihtelevat jonkin verran riippuen käytetystä kehitystyökalusta, mutta yleisimmät niistä ovat melko vakiintuneita (ks. esim. https://getfirebug.com/wiki/index.php/Console_API)

Takaisin ylös

5.3 Operaattorit

Lausekkeet ovat sellaisia koodinosia, jotka voidaan evaluoida ja evalutoituina ne edustavat jonkin tietotyypin arvoa. Seuraavassa on luetteltu JavaScriptin lausekkeissa käytettävissä olevat operaattorit.

5.3.1 Aritmeettiset operaattorit

Operaattori Selitys Esimerkki
(a=5 ja b=10)
+ yhteenlasku a + b arvo 15
-vähennyslasku a - b arvo -5
* kertolasku a * b arvo 50
/ jakolasku a / b arvo 0.5
% jakojäännös (modulus) a % b arvo 5
++ lisäysoperaattori, kasvattaa arvoa yhdellä a++ lausekkeen arvo 5, mutta a:n arvo lausekkeen suorituksen jälkeen 6
-- vähennysoperaattori, vähentää arvoa yhdellä a-- lausekkeen arvo 5, mutta a:n arvo lausekkeen suorituksen jälkeen 4

5.3.2 Vertailuoperaattorit

Operaattori Selitys Esimerkki
(a=5, b=10 ja c="10")
== yhtäsuuruus (a == b) arvo false
(b == c) arvo true
=== yhtäsuuruus ja tyyppivertailu (b === c) arvo false
!= erisuuruus (b != c) arvo false
!== erisuuruus ilman tyyppivertailua (b !== c) arvo true
> suurempi kuin (a > b) arvo true
< pienempi kuin (a < b) arvo false
>= suurempi tai yhtä kuin. (a > b) arvo true
<= pienempi tai yhtä kuin (a < b) arvo false

5.3.3 Loogiset operaattorit

Operaattori Selitys Esimerkki
(a=true ja b=false)
&& looginen and-operaattori (a && b) arvo epätosi
|| looginen or-operaattori (a || b) arvo tosi
! negaatio !(a && b) arvo tosi

5.3.4 Binääriset operaattorit

Operaattori Selitys Esimerkki
(a=2 ja -b=3)
|binäärinen or - suorittaa loogisen or-operaation kokonaislukuoperandiensa binäärimuotojen vastinbiteille (a | b) arvo 3
^binäärinen xor (poissulkeva or) (a ^ b) arvo 1
~ binäärinen negaatio, vaihtaa luvun binääriesityksen nollat ykkösiksi ja päinvastoin (~a) arvo -3 ja (~b) arvo -4
<< binäärinen siirto vasemmalle, siirtää ensimmäinen operandin binäärimuotoisessa esityksessä bittejä toisen operandin ilmoittaman määrän, täyttää oikealta 0-biteillä (a << 1) arvo 4
>> binäärinen siirto oikealle, siirtää ensimmäinen operandin binäärimuotoisessa esityksessä bittejä toisen operandin ilmoittaman määrän, täyttää vasemmalta 0-biteillä, jos luku on positiivinen luku, 1-biteillä jos se on negatiivinen luku (säilyttää luvun etumerkin) (a >> 1) is 1
>>> binäärinen siirto oikealle, täyttö 0-biteillä, muuten sama kuin edellä, mutta täyttö vasemmalta aina 0-biteillä (a >>> 1) arvo 1

5.3.5 Sijoitusoperaatorit

Operaattori Selitys Esimerkki
= arvon sijoitus c = a + b antaa lausekkeen a+b arvon muuttujalle c
+= lisää oikean operandin arvon vasemman operandin arvoon c += a <=> c = c + a
-= vähentää oikean operandin arvon vasemman operandin arvosta c -= a <=> c = c - a
*= kertoo vasemman operandin arvon oikean operandin arvolla c *= a <=> c = c * a
/= jakaa vasemman operandin arvon oikean operandin arvolla c /= a <=> c = c / a
%= korvaa vasemman operandin arvon jakolaskun vasen/oikea jakojäännöksellä c %= a <=> c = c % a

5.3.6 Erityiset operaattorit

Kielessä on edellisten lisäksi seuraavat erityiset operaattorit

Operaattori Selitys Esimerkki
? : ehdollisuusoperaattori (?) evaluoi ensimmäisen operandinsa arvon, jos arvo on true, koko lausekkeen arvo on toisen operandin arvo, jos false, arvo on kolmannen operandin arvo ehto?arvo1:arvo2
Jos ehto on tosi, lausekkeen arvo on arvo1, muuten arvo2
typeof unaarinen operaattori; lausekkeen arvo on operandin tyyppi merkkijonona annettuna typeof luku1
lausekkeen arvo "number" jos muuttujassa oli luku; muut mahdolliset arvot ovat: "string", "function", "boolean", "object"
, pilkkuoperaattori (,) evaluoi molemmat operandinsa ja palauttaa lausekkeen arvona toisen operandin arvon i++, j++
kasvattaa molempien operandiensa arvoa lausekkeen itsensä arvo on j (ennen kasvatusta)
delete unaarinen operaattori, tuhoaa operandinsa, joka voi olla olio, olion ominaisuus tai taulukon solu; tuhottu kohde on onnistuneen tuhoamisen jälkeen undefined ja lausekkeen arvo on true, jos tuhoaminen ei onnistunut, lausekkeen arvo on false delete olio; delete olio.ominaisuus; delete taulukko[i];
in palauttaa arvon true, jos ensimmäinen operandi (ominaisuus) on toisen operandin (olio) ominaisuus length in mjono
arvo on true, jos mjono on String-olio, jolla on ominaisuus length
instanceof palauttaa arvon true, jos ensimmäinen operandi (olio) on toisen operandin (tietotyyppi) pohjalta luotu olio var auto = new String("Lada");
lausekkeen auto instanceof String
arvo on true
new unaarioperaattori olion luontiin jonkin itsemääritellyn tai ennalta määritellyn oliotyypin mukaisesti. var olio = new Date(2013,4,26);
this unaarioperaattori jonka arvona on nykyinen olio; yleensä käytetään metodin sisällä kun halutaan viitata kutsuvaan olioon this.ominaisuus1
void unaarioperaattori, jota käytetään kun halutaan evaluoida lauseke, ilman että lausekkeelle annetaan arvoa void (lauseke)

Takaisin ylös

5.4. Muuttujat ja tietotyypit

JavaScript on dynaamisesti tyypitetty kieli, joka tarkoittaa sitä, että muuttujien tyyppi määräytyy sen arvon perusteella.

JavaScriptissä on perustietotyypit luvuille, merkkijonoille ja totuusarvoille ja sen lisäksi oliot. Olioita voi luoda itse, mutta kielestä löytyy joukko valmiiksi käytettävissä olevia ydinolioita (built-in objects). Käsitellään seuraavassa muuttujien esittelyt ja perustietotyypit. Oliohin palataan kontrollirakenteiden esittelyn jälkeen.

Perustietotyypit ovat yksinkertaisia muuttujia, jotka luodaan sijoittamalla suoraan arvo (numero, merkkijono, totuusarvo) muuttujaan. Esimerkiksi

var mjono = "Jokin merkkijono";

On kuitenkin hämmentävää, että tälle string-tietotyyppiselle muuttujalle voi kohdistaa metodeja, kuten esimerkiksi

var mjono = "Jokin merkkijono";
var mjono2 = mjono.toUpperCase();

Tämä johtuu siitä, että ydinoliot Number, String ja Boolean ovat perustietotyyppien number, string ja boolean ympärille määriteltyjä olioita, ja jos primitiiviin kohdistetaan vastaavan olion metodeja, perustietotyyppi muunnetaan automaattisesti väliaikaisesti, metodin suorituksen ajaksi, olioksi.

5.4.1 Muuttujien esittelyt

Muuttuja on esiteltävä ennenkuin sitä voi käyttää. Muuttujan nimissä saa käyttää kirjaimia (A-Z, a-z), numeroita (0-9) ja alaviivamerkkiä (_). Nimi ei saa kuitenkaan alkaa numerolla: esimerkiksi muuttujan nimi 99tunnus ei siten ole sallittu, mutta tunnus _99tunnus on.

Muuttujalle voidaan antaa arvo esittelyn yhteydessä, mutta se ei ole välttämätöntä.

<script type="text/javascript">
   var luku = 12; //Number
   var nimi = "Outi Ohjelmoija";
   var nimi2;
</script>

Muuttujat ovat paikallisia omassa funktiossaan. Funktioiden ulkopuolella esitellyt muuttujat ovat globaaleja muuttujia. Vaikka muuttujan voi esitellä funktion sisällä missä kohdassa tahansa (jopa sen jälkeen kun sitä käytetään! ‐ ks. hoisting), on hyvän käytännön mukaista esitellä funktiossa käytetyt muuttujat funktion alussa.

Jos muuttujalle sijoitetaan arvo esittelemättä sitä lainkaan, siitä tulee automaattisesti globaali muuttuja. Esim.

<script type="text/javascript">
   var a = 5; 
   var xSumma = 0;
   function  TekeeJotain () {
      var b = 12; // paikallinen muuttuja, olemassa vain funktion sisällä  
      c = 10;     // globaali muuttuja, olemassa myös funktion ulkopuolella
      ....
    }
</script>

Tästä syystä muuttujien nimiä kirjoitettaessa tulee olla tarkkana, kirjoitusvirhe muuttujaa käytettäessä luo helposti uuden muuttujan. Esimerkiksi yllä, jos funktion sisälle kirjoittaisimme sijoituslauseen: xsumma=b+c; se loisi uuden globaalin muuttujan xsumma.

5.4.2 Perustietotyypit (Primitive Data Types)

Muuttujalle sijoitettavalla arvolla on aina tietotyyppi. JavaScriptin perustietotyypit ovat: number, string, boolean ja undefined.

Luvut – number

JavaScriptissä ei ole erikseen tietotyyppiä kokonais- ja desimaaliluvuille, luvussa voi olla desimaaliosa tai se voi puuttua. Jos luvussa ei ole desimaalipistettä, niitä kohdellaan kokonaislukuina.

var luku1 = 3535;
var luku2 = -1000;
var luku3 = 0.34544;
var luku4 = 12.3e-2; // = 0.123

Luvut on esitetty oletusarvoisesti desimaalijärjestelmässä, mutta jos luku alkaa numerolla 0, se tulkitaan oktaaliluvuksi (kantalukuna 8). Jos luvun alussa olevaa nollaa seuraa, luku tulkitaan heksadesimaaliluvuksi.

var n1 = 0377; // luku on 255
var n2 = 0xff; // tämäkin luku on 255 

Lisäksi luku-tyyppinen muuttuja voi saada arvokseen erityiset lukuvakiot Infinity, -Infinity tai NaN. Nämä erityiset vakiot ovat siis tyypiltään lukuja (tietotyyppiä number) ja arvoa +/-Infinity käytetään, jos luku on suurempi kuin mitä number-arvoina voi ilmaista. Suurin luku on itseasiassa luku 1.7976932348623157e+308 ja pienin on -5e324. Suurimmassa luvussa siis voi olla 308 nollaa, mutta 309 nollaa sisältävä luku on jo sama kuin infinity.

var n3 = 1e309; 
var tooBig = (n3 == infinity) // muuttujan tooBig arvo on true
var n4 = 3 / 0;               // nollalla jako tuottaa arvon infinity
var n5 = n3 / 100;            // mikä tahansa laskuoperaatio jossa yksi 
                              // operandeista infinity, tuottaa arvon infinity
var n6 = 10 * "12";           // n6 saa arvokseen 120
var n7 = "10" * "12";         // n7 saa arvokseen 120
var n8 = 10 * "ff";           // n8 saa arvokseen NaN (not a number)
typeof n8;                    // tyyppi on "number"

Normaalisti aritmeettisissa lausekkeissa operandit muunnetaan automaattisesti luvuiksi, jos se on mahdollista. Poikkeuksena merkkijonojen ja lukujen yhteenlasku, konkatenaatio, josta tarkemmin alla, merkkijonojen yhteydessä. Jos yritetään evaluoida aritmeettista lauseketta, mutta kaikkia sen operandeja ei voida tulkita luvuiksi, saa arvokseen NaN. Arvo Nan tarkoittaa että arvo ei ole lukuarvo - paradoksaalista kyllä, sen tietotyyppi on kuitenkin number.

Merkkijonot – string

Merkkijonot koostuvat lainausmerkkien tai heittomerkkien väliin kirjoitetuista merkeistä ja mahdollisesti erikoismerkeistä (escape characters). Erikoismerkit kirjoitetaan kenoviivan avulla; yleisimpiä erikoismerkkejä ovat:

var mjono1 = "Tämä on merkkijono";
var mjono2 = 'Tähän  sisältyy "lainaus", siksi käytetty heittomerkkejä';
var mjono3 = "Tähän  sisältyy \"lainaus\", sen voi tehdä myös näin"'";
var mjono4 = "123"; // tämäkin on merkkijono, ei luku
var mjono5 = "ensimmäinen rivi\ntoinen rivi"; // tulostuisi kahdelle riville

Merkkijonojen yhteydessä plus-operaattori (+) tulkitaan konkatenaatio-operaattoriksi, sen avulla merkkijonoja voi yhdistää. Jos plus-operaattoria käytettäessä toinen operandeista on merkkijono, operaattori tulkitaan konkatenaatioksi ja toinenkin operandi muunnetaan merkkijonoksi.

var alku = "Tässä on alku";
var loppu = "ja tässä loppu";
var mjono = alku + ", " + loppu + "."; // ="Tässä on alku, ja tässä loppu."
var sostunnus = "120289-123C"; 
var vuosi = sostunnus.substr(4, 2); // vuosi on "89"
var svuosi = "19" + vuosi; // merkkijono "1989"
var vuosi2 = vuosi + 1;   // merkkijono "891"
var vuosi3 = parseInt(vuosi) + 1;   // luku 90
var vuosi4 = Number(vuosi)+1; // luku 90
var luku = 12;  // luku 12
var luku = 12 + "" // merkkijono "12"

Totuusarvot – boolean

Totuusarvoisen tietotyypin madolliset arvot ovat true ja false, arvoja ei suljeta heittomerkkeihin.

var aviossa = false;
if (aviossa) { .. } // ehto on epätosi, lausetta ... ei suoriteta
var b = (luku === 1000) // b:n arvo tosi, jos mjalla luku arvona luku 1000

Jos muista tietotyypeistä tehdään automaattikonversioilla boolean-perustietotyyppi, muunnetaan tyhjä merkkijono (""), luku 0, arvot undefined, null ja NaN totuusarvoksi false, kaikki muut arvot totuusarvoksi true.

var str1 = "jotain";
if (str1) { ...    // ehto on tosi
var str2 = "false";
if (str2) { ...    // tämäkin ehto on tosi
var n=0;
var b = !!(n); // = false
    b = !!(str2); // = true 

Määrittelemätön – undefined

Vain yhden arvon, arvon undefined sisältävä tietotyyppi. Jos yrität käyttää sellaisen muuttujan arvoa, jota ei ole määritelty silloin käytetään arvoa undefined. Samoin tapahtuu, vaikka muuttuja olisi määritelty, mutta sille ei ole sijoitettu vielä arvoa, ts. muuttujan määrittelyn yhteydessä se saa arvon undefined, jos se jätetään alustamatta.

Muuttuja voidaan alustaa tyhjäksi. Muuttujalle, jonka arvon ei haluta olevan määrittelemätön, mutta sille ei myöskään haluta antaa mitään tiettyä arvoa, voidaan antaa arvoksi null.

Takaisin ylös

5.5 Lauseet (kontrollirakenteet)

Seuraavassa eisitellään lyhyesti JavaScriptissä käytössä olevat kontrollirakenteet, kielen lauseet. Erota lauseet toisistaan puolipisteellä.

Aluksi todettakoon, että peräkkäiset lauseet voidaan sitoa lohkolauseeksi käyttäen aaltosulkuja ( { } ). Tätä mahdollisuutta käytetään erityisesti silmukoiden yhteydessä sitomaan toistettavat lauseet yhdeksi toistettavaksi lohkolauseeksi. Huomaa, että muuttujien paikallisuus tarkoittaa paikallisuutta funktion sisällä, ei lohkolauseen sisällä. Vaikka muuttuja olisikin esitelty lohkolauseen sisällä, se on näkyvissä myös lohkolauseen ulkopuolella koko funktiossa.

5.5.1 if-lause

if-ehtolauseen avulla voidaan asettaa ehto, jonka perusteella valitaan mikä lause (voi olla lohkolause, jolloin lauseita voi olla useita) suoritetaan.

 if (ehtolauseke) 
   lause;
 [else 
   lause;]

Kuten muissakin kielissä, ehtolauseita kirjoittaa sisäkkäin

var ika;
. . .
  if (ika<10) {
    document.write("lapsi");
    //ehkä muitakin lauseita
  }
  else if (ika <30) {
  	document.write("nuori");
  	//ehkä muitakin lauseita
  }
  else if (ika <65) {
  	document.write("aikuinen");
  	//ehkä muitakin lauseita
  }
  else document.write("seniori");

5.5.2 switch-lause

switch-ehtolausekkeessa määritellään lauseke, jonka arvon perusteella voidaan määrittää mistä lähtien lauseen sisään kirjoitetut lauseet suoritetetaan. break-lause katkaisee lauseiden suorituksen ja kontrolli siirtyy pois switch-lauseesta.

switch (lauseke) {
  case arvo1: lause;
             [lause;]*
              break;
  case arvo2: lause;
             [lause;]*
              break;
   ...
  case arvon: lause;
             [lause;]*
              break;
  default: lause;
}

Esimerkiksi:

switch (luku) {
  case 10: alert("Luku on 10");
            break;
  case 20: alert("Luku on 20");
            break;
  default: alert("Ei ollut luku 10 eikä 20, ei.")
}

5.5.2 while-silmukka

while-silmukan avulla voidaan asettaa ehto, jonka voimassa olllessa silmukan lauseita suoritetaan: "niin kauan kun"

while (ehtolauseke)
   lause
   [;lause]*

Esimerkiksi:

var i=10;
while (i>0) {
    document.write(i+"\n");
    i--;
}

5.5.4 do..while-silmukka

do..while -silmukka on kuten while-silmukka edellä, mutta ehto tarkistetaan aina sen jälkeen kun silmukan lauseet on suoritettu. Tällöin lauseet tulee aina suoritetuksi ainakin kerran.

do{
  lause
  [;lause]*
} while (ehtolauseke);

5.5.5 for-silmukka

for -silmukan avulla voidaan aloituslause suoritus, lopetusehto ja silmukkamuuttujan arvon muutos jokaisella silmukan kierroksella määritellä suoraan lauseen esittelyssä seuraavasti.

for (aloituslause; lopetuslause; silmukkamuutujan muutos){
   lause
  [;lause]*
}

Esimerkiksi:

for (var i=0; i<10; i++)
  document.write(taulu[i,j])
}

5.5.6 for..in -silmukka

for..in -silmukan avulla käydään läpi olion kaikki ominaisuudet. Silmukan lauseet suoritetaan kullekin ominaisuudelle erikseen.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>for..in -esimerkki</title>

</head>
<body>
  <h1>Opetellaan JavaScriptiä - 6 </h1>
  <p> 
    Käydään läpi asiakastiedot ja muodostetaan niistä
    merkkijono. 
  </p> 
  <p> 
    Tuloksena on: 
    <span id="tiedot-tahan">Ei tietoja.
  </p>
    <script>
      var tieto;
      var asiakasTiedot = "";
      var asiakas={etunimi:"Aarne",sukunimi:"Asiakas",ika:30};
      for (tieto in asiakas)
        asiakasTiedot = asiakasTiedot + asiakas[tieto] + " ";
        document.getElementById("tiedot-tahan").innerHTML =
        asiakasTiedot;
  </script></body>
</html>

Esimerkki www-sivuna

Yllä olevassa esimerkissä, ennen kutakin silmukan kierrosta muuttuja tieto saa arvokseen olion asiakas ominaisuuden. Silmukkaa toistetaan, kunnes olion kaikki kolme ominaisuutta on käyty läpi, ja ne on yhdistetty yhdeksi merkkijonoksi.

Esimerkissä tutustutaan myös kahteen uuteen monikäyttöiseen DOM-rajapinnan kohteeseen: document-olion metodiin getElementById ja DOM-solmun ominaisuuteen innerHTML.

Aiemmin olemme tutustuneet jo yhteen document-olion metodiin, funktioon write. Toinen voimakas document-olion metodi on getElementById. Sen avulla voi noutaa minkä tahansa yksittäisen elementin, jolle on annettu attribuutti id. Tässä tilanteessa funktion avulla haettiin <span>-elementti tunnuksella "tiedot-tahan".

Toinen käytetty DOM-funktio, innerHTML jäsentää merkkijonon (tässä esimerkissä merkkijonomuuttujan asiakasTiedot sisällön) ja korvaa solmun (tässä <span>-elementti) sisällön jäsennetyllä sisällöllä.

5.5.7 break ja continue

break-lausetta käytettiin jo edellä switch-lauseen yhteydessä, sen avulla suoritus määrättiin siirtymään ulos lauseesta. break-lauseen avulla voidaan tulla ulos mistä tahansa lauselohkosta. continue lausetta käytetään silmukan sisällä määräämään, että suoritus siirtyy välittömästi testaamaan silmukkaehdon voimassaolemista, ja mikäli se vielä täyttyy, silmukan lauseiden suoritusta jatketaan seuraavan kierroksen alusta.

Takaisin ylös

5.6 Oliot

Oliot ovat kokoelmia piirteitä; piirteet voivat olla joko ominaisuuksia (properties, perustietotyyppejä tai edelleen olioita) tai metodeja (methods, funktioita). Olion ominaisuuksiin ja metodeihin viitataan pistenotaatiolla.

5.6.1 Olion luominen ja siihen viittaaminen

Olio, voidaan luoda joko alustajalla (initializer) tai new-operaattorin ja Object()-konstruktorin avulla. Alustajalla olion luominen tapahtuu luettelemalla listana piirteenNimi:arvo-pareja aaltosulkeissa, esimerkiksi:

var kirja1={aihe:"jQuery", tekija:"David Sawyer McFarland"};

Seuraavassa esimerkissä luodaan alustajalla kirja-olio ja kirjoitetaan sivulle sen tiedot sivulle viittaamalla sen ominaisuuksiin pistenotaatiolla olio.ominaisuus.

<!DOCTYPE HTML>
<head>
  <title>Itse määritelty olio</title>
</head>
<body>
  <h1>Opetellaan JavaScriptiä - 2</h1>
  <script type="text/javascript">
     // Luodaan ja alustetaan kirja-olio, jolla kaksi ominaisuutta
     var kirja = {aihe:"jQuery", tekija:"David Sawyer McFarland"};
     // kutsutaan seuraavassa luotua oliota
     document.write("Kirjan nimi: " + kirja.aihe + "<br>");
     document.write("Kirjoittaja: " + kirja.tekija + "<br>");
  </script></body>
</html>

Esimerkki www-sivuna

Edellisessä tilanteessa olio luotiin suoraan alustus-lyhennemerkintätapaa käyttäen. Olio voidaan tarvittaessa luoda myös ensin tyhjänä oliona käyttäen new-operaattoria,

olio = new Object();

ja antamalla sen jälkeen oliolle ominaisuudet sijoituslausein:

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <title>Oma olio</title>
</head>
<body>
  <h1>Opetellaan JavaScriptiä - 3</h1>
  <script type="text/javascript">
      var kirja = new Object();   // Luodaan olio
      kirja.aihe = "jQuery";      // ja asetetaan oliolle ominaisuudet
      kirja.tekija  = "David Sawyer McFarland";
     // kutsutaan seuraavassa otsikko-osassa luotua oliota
     document.write("Kirjan nimi: " + kirja.aihe + "<br>");
     document.write("Kirjoittaja: " + kirja.tekija + "<br>");
  </script></body>
</html>

Esimerkki www-sivuna

Selkeämpää on kuitenkin luoda erillinen konstruktori-funktio Olio(parametrit). Silloin voimme luoda kirja-olion suoraan konstruktoria käyttäen vain tarvittaessa. Lisäksi, saman konstruktorin avulla voidaan luoda muitakin kirja-oliomuuttujia. Funktion sisällä käytettäessä varattua sanaa this, se viittaa funktiota kutsuvaan olioon, seuraavassa esimerkissä siis kostruktorin avulla luotavaan olioon.

<!DOCTYPE HTML>
<head>
  <meta charset="UTF-8">
  <title>Konstruktorilla määritelty olio</title>
  <script type="text/javascript">
    //kostruktori: funktio Kirja-tyyppisen olion luomiseksi
    function Kirja(mj1, mj2){  
      this.aihe = mj1; 
      this.tekija  = mj2;
    }
  </script></head>
<body>
  <h1>Opetellaan JavaScriptiä - 4</h1>
  <script type="text/javascript">
    var kirja1 = new Kirja("jQuery","David Sawyer McFarland");
    document.write("<p class='tausta'>Kirjan nimi: " + kirja1.aihe + "<br>");
    document.write("Kirjoittaja: " + kirja1.tekija + "<br>");
  </script></body>
</html>

Esimerkki www-sivuna

Edellä oliolle annettiin luotaessa ominaisuuksia. Olioiden metodien määritys tapahtuu myös samaan tapaan, nyt metodille vaan annetaan arvoksi funktio. Voit siis ajatella, että olion metodit ovat sellaisia olion ominaisuuksia, jolle ei annetakaan arvoksi perustietotyyppiä, vaan funktio.

Seuraavaan esimerkkiin on kirjoitettu kontstruktori oliolle Asiakas, jolla on kolme ominaisuutta: etunimi, sukunimi ja ika. Neljäs oliolle osoitettu piirre syntynyt ei olekaan perustietotyyppi, vaan sille annetaan arvoksi funktion nimi (Syntymavuosi). Metodin toteuttava funktio Syntymavuosi on määritelty erikseen.

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title>metodin itse tehdylle oliolle</title>
    <script type="text/javascript">
      function Asiakas(mj1, mj2, luku) {
          this.etunimi = mj1; 
          this.sukunimi  = mj2;
          this.syntynyt = luku; 
          // liitetään olioon metodi
          this.ika = laskeIka;        
      } 
      function laskeIka() {
        var nyt = new Date();
        var vuotta = nyt.getFullYear() - this.syntynyt;
        return vuotta;
      }
    </script></head>
<body>
  <h1>Opetellaan JavaScriptiä - 5</h1> 
  <script type="text/javascript">
      var asiakas_001 = new Asiakas("Kalle", "Kuluttaja", 28);
      document.write("Asiakkaan 001 nimi on: " + 
        asiakas_001.etunimi + " " + asiakas_001.sukunimi + 
        " (" + asiakas_001.ika + "v)" +
        ",  hän on siis syntynyt vuonna: " + asiakas_001.syntynyt(2013));
   </script>
</body>
</html>

Esimerkki www-sivuna

Olion ominaisuuteen voi viitata paitsi pistenotaatiolla, myös käyttäen hakasulkuja seuraavasti

  kirja1= new Kirja("Object-Oriented JavaScript", "Stefanov");
  kirjailija = kirja1.tekija; 
  kirjailija = kirja1["tekija"]; // myös tämä mahdollinen tapa

Jos ominaisuus tekijä olisi edelleen olio, jolla olisi ominaisuudet etunimi ja sukunimi, viittaukset tehtäisiin vastaavasti

  kirja1.tekija.etunimi="Stoyan";       // saa aikaan saman kuin
  kirja1["tekija"]["etunimi"]="Stoyan"; // tämä

Kuten edellä nähtiin, oliolle voi luoda uusia ominaisuuksia vain sijoittamalla niille arvon. Hakasulkunotaation avulla uuden ominaisuuden luonti oliolle tapahtuisi seuraavasti:

  kirja1.kustantaja= "Packt Publishing";    // saa aikaan saman kuin 
  kirja1["kustantaja"]= "Packt Publishing"; // tämä

5.6.2 Valmiit oliot (Built-in objects)

JavaScriptiin on valmiiksi määritelty joukko olioita. Ne voidaan jakaa kolmeen luokkaan:

  1. Tiedonpaketoija-oliot (data wrappers). Jos muuttujaa on tarpeen käsitellä oliona (käyttää esimerkiksi sen metodeja) voi perustietotyyppisen muuttujan määritelläkin olioksi perustietotyypin sijasta. Tällaisia "tiedonpaketoija-olioita" ovat Object, Number, Boolean, String, Array ja Function.
  2. Apuväline-olioiden Math, Date ja RegExp avulla päästään käsiksi moniin käteviin tietoihin ja toimintoihin.
  3. Virheolioiden avulla saadaan tietoa jonka avulla voidaan selviytyä odottamattomista tilanteista.

Object

Object-olio on kaikkien JavaScript-olioiden äiti, mikä tarkoittaa sitä, että jokainen luotu olio perii sen ominaisuudet ja metodit. Aiemmin nähtiin, että tyhjä olio voidaan luoda konstruktorifunktiolla Object(), sen voi luoda myös literaalinotaatiolla.

var o1 = new Object():
var o2 = {}; // o1 ja o2 samanlaisia tyhjiä olioita

Tyhjälläkin oliolla on joukko metodeja ja ominaisuuksia, kuten esimerkiksi toString tai valueOf

o1.toString(); // palauttaa olion merkkijonoesitysmuodossa
o1.valueOf(); // palauttaa olion itsensä

Tarkemmin tähän Object-perusolioon, voit tutustua esimerkiksi MSDN- ja Tutorialspoint-sivuilla
[MDN] [TP]

Number

Jos luku-muuttujaa on tarpeen käsitellä oliona (käyttää esimerkiksi sen metodeja) voi sen esitellä perustietotyypin number sijasta olioksi Number. Number-oliolle valmiiksi määriteltyjä metodeja ovat esimerkiksi

var luku= new Number(15.565);
var mja1 = n.toFixed(); // = ”16” pyöristys kokonaisluvuksi
var mja2 = n.toFixed(1); // = ”15.6” yksi desimaali
var mja3 = n.toExponential(3) // = "1.556e+1" 

Number-oliolla myös ominaisuuksia, esim.

var luku1 = Number.MAX_VALUE;       // 1.7976931348623157e+308
var luku2 = Number.MIN_VALUE;       // = 5e-324
var luku3 = Number.MIN_VALUE*1000;  // = 4.94e-321

Tarkemmin Number-olion ominaisuuksista ja metodeista:

[MDN] [W3School] [TP]

Boolean

Vastaavasti totuusarvoisen perustietotyypin boolean sijasta voi tarvittaessa luoda olion Boolean.

var mja = new Boolean(totuusarvo);

On tärkeätä muistaa, että konstruktori luo Boolean-olion, ei perustietotyyppiä boolean. Toisin sanoen yllä, typeof b on object, ja ei-tyhjän olion automaattikonversio boolean-perustietotyyppiin on aina true.

var b1 = new Boolean(true);
var b2 = new Boolean(false);  

if (b1) {lause1; ..  // suoritetaan lause1
if (b2) {lause1; ..  // suoritetaan myös lause1

if (b1.valueOf()) {lause1; ..  // suoritetaan lause1
if (b2.valueOf()) {lause1; ..  // ei suoriteta lausetta lause1

Jos Boolean-oliolle ei anneta alkuarvoa parametrina, tai jos sille annetaan arvo, joka konvertoituu boolean-perustyypiksi arvona false (0, null, "", undefined, NaN tai ehto, jonka arvo on epätosi), saa muuttuja arvokseen false. Kaikissa muissa tilanteissa saa luotu muuttuja arvokseen true.

Tarkemmin Boolean-olion ominaisuuksista ja metodeista:
[MDN] [W3School] [TP]

Array

Tarkastellaan taulukkoa ennen merkkijono-oliota String, koska String on itse asiassa toteutettu taulukkona merkkejä.

Taulukko on järjestetty joukko arvoja, joihin voi viitata joko nimellä tai indeksillä. Taulukko voidaan luoda usealla eri tavalla. Seuraavat kolme lausetta loisivat saman taulukon:

var taulu = new Array(alkio0, alkio1, ..., alkioN);
var taulu = Array(alkio0, alkio1, ..., alkioN);
var taulu = [alkio0, alkio1, ..., alkioN];

Taulukko on JavaScriptissä toteutettu oliona (voit kokeilla antamalla lausekkeen typeof taulu).

Jos taulukko halutaan luoda antamatta sille sisältöä luonnin yhteydessä, sille voi halutessaan antaa pituuden taulukon luonnissa. Esimerkiksi kolmen alkion mittainen sisällöltään määrittelmätön taulukko voidaan luoda ja täyttää seuraavasti.

var taulu = new Array(3);
var taulu = Array(3),
var taulu = []; taulu3.length = 3;

taulu[0]=alkio1; taulu[1]=alkio2; taulu[2]=alkio3;

JavaScriptille tyypilliseen tapaan, taulukko on hyvin dynaaminen. Enempää sen pituus kuin alkioiden tyyppikään ei ole kiinnitetty. Taulukko voi siis sisältää eri tyyppisiä alkioita ja alkioiden tyypit ja lukumäärä voivat muuttua dynaamisesti.

var tmp = new Array(1, "mjono", true);
   
tmp.length = 10;   // taulukon pituus kasvoi ja alkioiden 
                   // tmp[3].. tmp[9] arvo on undefined
tmp[100] = false;  // sijoitus kasvattaa taulukon 100 alkion 
                   // mittaiseksi, tmp[3].. tmp[98] arvoltaan undefined

Moniulotteisia taulukoita voi luoda sisäkkäisten taulukkojen avulla, ts. määrittelemällä yksiulotteisen taulukon soluun edelleen taulukon.

var iMax = 5;
// silmukkamuuttujat
var i, j;

// Määritellään taulukon pituudeksi iMax+1. 
// (taulukon indeksointi lähtee nollasta)
var matriisi = new Array(iMax + 1);

//  kullekin taulukon riville
for (i = 1; i <= iMax; i++) {  
  // Luo taulukon sarakkeet
  matriisi[i] = new Array(iMax + 1);

  // täytetään taulukko 
  for (j = 1; j <= iMax; j++) {
    matriisi[i][j] = i * j;
  }
}

document.write(matriisi[3][4]);
document.write("<br/>"); 
document.write(matriisi[5][2]);
document.write("<br/>");
document.write(matriisi[1][4]);

Edellinen ohjelma tuottaisi luvut 12 10 ja 4 (omille riveilleen).

Taulukolle on määritelty joukko metodeja, joiden avulla taulukkoja voidaan mm. lajitella, paloitella, yhdistellä, käyttää pinomaisena tietotyyppinä, jne. Metodiluetteloita ja tarkempia kuvauksia löydät seuraavista lähteistä:
[MDN] [W3School] [TP]

String

Myös merkkijononmuuttujan voi luoda olioksi.

var str = new String("Tämä on merkkijono-olio");

Merkkijonon luonti olioksi, antaa laajat mahdollisuudet merkkijonon käsittelyyn. String-olion metodien avulla voi merkkijonosta esimerkiksi hakea merkkejä, korvata merkkejä toisilla, palotella merkkijono osamerkkijonoiksi, muuntaa kirjaimet pieniksi tai isoiksi aakkosiksi, jne. Esimerkkejä merkkijonojen käsittelemisestä annettiin edellä perustietotyypin string yhteydessä.

Tarkemmin String-olion ominaisuuksista ja metodeista:
[MDN] [W3School] [TP]

Date

Date-oliotyyppiseen muuttujaan voi tallentaa päiväyksen ja ajan. Ajan voi ilmaista oliomuuttujaa luodessaan antamalla konstruktorille jokin seuraavista parametreista

Jos Date-olio luodaan ilman parametreja, saa luotu olio arvokseen nykyisen ajanhetken.

var luotu = new Date(2013, 04, 27, 16, 02, 00);
var nyt = new Date();

Date-olion metodien avulla voi esimerkiksi noutaa nykyhetken päväyksen (käyttäjän laitteen antama "system clock" -aika) ja päästä helposti eri osiin päiväystä, kuten esimerkiksi viikonpäivään getDay(). Tarkemmin Date-olion ominaisuuksista ja metodeista:
[MDN] [W3School] [TP]

Math

Math-olioille on määriteltynä ominaisuuksia ja metodeita, jotka helpottavat matemaattisten operaatioiden tekemistä. Olioon on esimerkiksi tallennettu piin (Π) arvo ominaisuudeksi: Math.PI ja trigonometriset funktiot metodeiksi. Math-prototyypistä ei normaalisti ole tarvetta luoda oliota, vaan sen ominaisuuksia ja metodeita voi käyttää suoraan prototyypistä.

var pii = Math.pii;
var rad = sin(x); // x kokonaisluku, palauttaa arvon sin(x) radiaaneissa 

Tarkemmin Math-olion ominaisuuksista ja metodeista:
[MDN] [W3School] [TP]

RegExp

RegExp-oliot ovat säännöllisiä lausekkeita, joiden avulla on mahdollista tehdä hahmontunnistusta merkkijonoille.

Lisää tietoa säännöllisistä lausekkeista:
[MDN] [W3School] [TP]

Takaisin ylös

5.7 Funktiot

Sekvenssi lauseita voidaan sulkea funktioksi, jolle annetaan nimi. Tällöin lauseet saadaan suoritetuksi mistä tahansa kohdasta, jossa funktiota kutsutaan. Funktion kutsua voidaan siis toisaalta ajatella lauselohkona, joka suoritetaan funktion kutsulla.

Funktioon voidaan syöttää kutsukohdasta arvoja parametrien avulla. Funktio esitellään käyttäen varattua sanaa function. Funktion parametrit luetellaan pilkulla eroteltuna funktion-nimen jälkeen sulkeissa. Sulkeet kirjoitetaan, vaikka funktiolla ei olisikaan parametreja, sulkeiden väliin ei silloin kirjoiteta mitään.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Asiakastietojen käsittely</title>
</link>
  <script type="text/javascript">
    function asiakas(mj1, mj2, luku) {
      this.etunimi = mj1; 
      this.sukunimi  = mj2;
      this.ika = luku; // vie metodi ominaisuudeksi 
    } 
    function muodostaAsiakasTiedot(tamaAsiakas){
      var tieto;
      var asiakasTiedot = "";
      for (tieto in tamaAsiakas)
        asiakasTiedot = asiakasTiedot + tamaAsiakas[tieto] + " ";
      return asiakasTiedot;
    }
  </script></head>
<body>
  <h1>Opetellaan JavaScriptiä - 7 </h1>
  <p> 
    Käydään läpi asiakastiedot ja muodostetaan niistä
    merkkijono. 
  </p> 
  <p> 
    Tuloksena on: 
    <span id="korvaa-tama">Ei tietoja.</span>
  </p>	
  <script type="text/javascript">
    var asiakas1 = new asiakas("Kalle", "Kuluttaja", 28);
    var str1 = muodostaAsiakasTiedot(asiakas1); 
      document.getElementById("korvaa-tama").innerHTML=str1;
  </script>
</body>
</html>

Esimerkki www-sivuna

Funktion saamat arvot, jotka erotellaan funkion kuvauksessa pilkulla, voidaan nimetä vapaasti. Parametrinä annettu muuttujan nimi on vain funktion itsensä käytössä. Muutama esimerkki.


function summa(arvo1, arvo2)
{
	return arvo1 + arvo2;
}

var tulos1 = summa(1, 2); // 3

var arvo1 = 1;
var arvo2 = 2;
var tulos2 = summa(arvo1, arvo2); //3

var arvo3 = 3;
var arvo4 = 10;
var tulos3 = summa(arvo3, arvo4); //13

Yllä olevassa esimerkissä on annettu funktiolle parametreinä suoraan numeroita, saman nimisiä muuttujia, kuin funktiossakin on ja erinimisiä muuttujia, kuin funktiossa on. Kaikki toimivat loogisesti. Parametrien nimet ovat siis vain funktion omia viittauksia saamiinsa arvoihin, eikä parametrien nimien tarvitse olla samoja, kuin funktion ulkopuolisten muuttujien nimet. Keskimmäisessä kutsussa on kuitenkin huomattava myös se, että se toimisi, vaikka funktiolle ei annettaisi mitään parametreja, koska muuttujat arvo1 ja arvo2, joita funktiossa lasketaan yhteen ovat määritely globaaleiksi muuttujiksi, joten funktiolla olisi niihin pääsy muutenkin. Esimerkissä funktio ei kuitenkaan käytä globaaleja muuttujia, vaikka se käyttääkin saman nimisiä muuttujia ja sillä olisi niihin pääsy. Tämä johtuu siitä, että funktion sisällä käytetään saman nimisiä lokaaleja muuttujia, jotka vain saavat globaalin muuttujan arvot. Mutta tästä on lisää kohdassa enkapsulaatio, jossa katsotaan tarkemmin globaalia, lokaalia ja muuttujien etsintäjärjestystä.

Edellisessä esimerkissä funktio palauttaa arvon return -lauseella. Funktioiden ei kuitenkaan ole pakko palauttaa mitään arvoa. Esimerkin funktio voisi esimerkiksi vain laskea arvot yhteen ja ilmoittaa tuloksen suoraan käyttäjälle vaikkapa alertilla tai kirjoittamalla summan dokumentin tekstiin.

Funktioita voidaan määritellä muutamallakin eri tavalla JavaScriptissä ja näillä eri määrittelytavoilla on eroavaisuuksia käytössä. Funktio voidaan kirjoittaa yllä olevalla tavalla, se voidaan kirjoittaa muuttujana (expression) ja se voidaan kirjoittaa automaattisesti ajettavana (self-invoking). Katsotaan esimerkkejä näistä.



// Function declared
function summa(arvo1, arvo2)
{
	return arvo1 + arvo2;
}

// Function defined as an expression (is actually an anonymous function)
var tulo = function(arvo1, arvo2)
{
	return arvo1 * arvo2;
};

var tulos1 = summa(1, 2); //3
var tulos2 = tulo(1, 2); //2

Jos funktio kirjoitetaan muuttujaksi, kuten esimerkin tulofunktiossa, ei sitä voi kutsua ennen funktion määrittelyä. Summafunktion kohdalla sitä voidaan kutsua myös ennen määrittelyä. Tämä johtuu siitä, että summafunktio määritellään jo skriptiä tulkittaessa (parse-time) ja tulofunktio määritellään vasta skriptiä suoritettaessa (run-time).


var tulos1 = summa(1, 2); //3 - Toimii

function summa(arvo1, arvo2)    
{
	return arvo1 + arvo2;
}

var tulos2 = tulo(1, 2); //2 - Ei toimi: Uncaught TypeError: undefined is not a function

var tulo = function(arvo1, arvo2)
{
	return arvo1 * arvo2;
};

Kolmas tapa kirjoittaa funktio on automaattisesti ajettava funktio (self-invoking function). Funktio on anonyymi ja se ajetaan välittömästi, eikä sitä tarvitse erikseen kutsua (se kutsuu itseään).


// Self-invoking anonymous function
(function() {
	alert("I invoked myself");
})();

Kolmannen määrittelyn mukainen funktio voi tuntua redundantilta, koska saman asian olisi voinut kirjoittaa jättämällä funktiomäärittelyn kokonaan pois. Tällä määrittelyllä on kuitenkin merkittävä rooli muuttujien enkapsulaatiossa.

Enkapsulaatio, Scope

JavaScriptiä kirjoitettaessa on tärkeä ottaa huomioon muuttujien scope. Jos muuttuja määritellään suoraan skriptiin (i.e. sitä ei kirjoiteta funktion sisään) se on globaali muuttuja. Globaalit muuttujat ovat käytettävissä kaikissa skriptin osissa ja myös kaikissa muissa JavaScript -tiedostoissa, joita sivulla ladataan. Näin ollen jos muuttuja var nimi = "Antti"; on kirjoitettu globaaliksi muuttujaksi voi jokin toinen ladattu JavaScript tiedosto määritellä sen uudelleen. Näin voi tulla vaikeaksi löytää virheitä koodista. Otetaan esimerkki globaalista ja lokaalista muuttujasta.


// Globaali muuttuja
var globalVar = "Tämä on globaali muuttuja";

var globalFunction = function()
{
	var localVar = "Tämä on lokaali muuttuja";
	alert(localVar); // Tämä on lokaali muuttuja
	alert(globalVar); // Tämä on globaali muuttuja
}

globalFunction();

alert(globalVar); // Tämä on globaali muuttuja
alert(localVar); // Uncaught ReferenceError: localVar is not defined

Koska localVar on määritelty funktion sisällä, se on lokaali tälle funktiolle, eikä sitä voida kutsua tai muuttaa funktion ulkopuolelta. Näin muuttuja on suojattu ulkopuoliselta (tahattomalta) muuttamiselta. JavaScript sallii funktioden kirjoittamisen funktioden sisälle, joten enkapsulaatiotasoja voidaan luoda rajaton määrä. Esimerkki funktioiden enkapsulaatiosta (function nesting).


// Globaali muuttuja
var globalVar = "Tämä on globaali muuttuja";

var globalFunction = function()
{
	var localFunction = function()
	{
		var superLocalVar = "Superlokaali muuttuja";
		alert(superLocalVar); // Superlokaali muuttuja
	}

	localFunction();

	var localVar = "Tämä on lokaali muuttuja";
	alert(localVar); // Tämä on lokaali muuttuja
	alert(globalVar); // Tämä on globaali muuttuja
	alert(superLocalVar); // Uncaught ReferenceError: superLocalVar is not defined
}

globalFunction();

alert(globalVar); // Tämä on globaali muuttuja
alert(localVar); // Uncaught ReferenceError: localVar is not defined
alert(superLocalVar); // Uncaught ReferenceError: superLocalVar is not defined

Nyt, kun funktioita on kirjoitettu useaan tasoon huomataan, että globaalit muuttujat ovat aina käytettävissä, mutta syvemmälle tasolle kirjoitetut muuttujat ovat vain sen tason ja sitä syvempien tasojen käytettävissä. Eri tasoilla voidaan käyttää samoja muuttujien nimiä. Tällöin JavaScript -tulkin tulee päätellä, mikä on haluttu muuttujan arvo. Muuttujan arvo etsitään aina ensin omalta tasoltaan (samalta tasolta, kuin missä sitä kutsutaan) ja sitten siirrytään seuraavalle tasolle aina globaaliin tasoon asti. Jos taas muuttuja löytyy, lopetetaan sen etsiminen. Esimerkiksi seuraavalla tavalla.


var nimi = "Antti";

var globalFunction = function ()
{
    var nimi = "Tapio";

    var localFunction = function ()
    {
        var nimi = "Sand";
        alert(nimi);
    }

    localFunction();
    alert(nimi);
}

globalFunction();
alert(nimi);

Tällöin saataisiin seuraavat viestit: Sand, Tapio, Antti. Jos taas kommentoitaisiin pois lokaalin funktion var nimi, ei kutsuvalla tasolla olisi kyseistä muuttujaa ja se etsittäisiin seuraavalta tasolta. Tällöin saataisiin viestit Tapio, Tapio, Antti. Ilmoituksia tulee kolme siksi, että alert kutsutaan kolmasti, eikä siksi, että saman nimisiä muuttujia on kolme. Tämänkaltainen saman muuttujanimen uudelleen määrittely eri arvoille eri kontekstissa ei kuitenkaan ole suositeltava koodauskäytäntö, sillä se saattaa tuoda helposti bugeja ohjelmaan ja tekee ohjelmakoodin lukemisesta ja ymmärtämisestä hankalampaa. Määrittelytasojen ymmärtämiseen se sen sijaan on melko hyvä esimerkki.

Kun nyt on käyty läpi muuttujien näkyvyyttä eri tasoille, alkaa self-invoking anonymous function -määrittelytapa näyttää hyödyllisemmältä. Sillä saadaan helposti kapsuloitua ja näin piilotettua muuttujia ja funktiota pois globaalista scopesta. Kirjoittamalla kaikki kyseisen tehtävän koodi sen sisään, voidaan estää muita sivulle ladattavia JavaScript -tiedostoja muuttamasta sen arvoja. Näin jos kaksi eri JavaScript -tiedostoa käyttävät samoja muuttujanimiä, ne eivät sotkeennu toisiinsa. Jos kirjoittaa kaiken koodin itse, pitänee itse kirjaa muuttujien nimistä, mutta jos haluaa jakaa koodiansa muille tai liittää sivuillensa muiden kirjoittamaa koodia, tällä tavoin voidaan suojata muuttujia.


(function ()
{
    var nimi = "Antti";

    var greetMe = function (nimi)
    {
        alert("Hello, " + nimi);
    }

    greetMe(nimi);
})();

alert("Can I also greet you, " + nimi + "?"); //Nope

Tässä esimerkissä ohjelman varsinainen koodi kirjoitettiin automaattisesti ajettavan anonyymin funktion sisälle. Se toimii samalla tavoin, kuin jos sitä ei olisi kirjoitettu funktion sisään lainkaan, mutta muuttujat ja funktiot ovat piilotettuja eivätkä enää globaaleja. Näin viimeinen alert -kutsu ei toimi, koska se on globaali, mutta muuttuja nimi ei ole globaali.

Hyvä JavaScript käytäntö: Don't litter the global scope

Takaisin ylös

5.8 Tapahtumat ja tapahtumankäsittelijät

Edellisissä esimerkeissä skriptipalat suoritettiin aina dokumentin latautuessa selaimeen, ts. ne oli kirjoitettu sivulle ns. inline-skripteinä. JavaScriptin varsinainen hyöty ja käyttökelpoisuus saadaan kuitenkin tapahtumien (event) ja niihin JavaScript-funktioina itse kirjoitettujen tapahtumankäsittelijöiden (event handler) kautta. Tapahtumankäsittelijöiksi kirjoitettujen funktioiden tulee jollain tavalla saada tieto tapahtumista.

Kun käyttäja tekee jotain sivulla, selain rekisteröi sen tapahtumana. Yksinkertaisin tällainen tapahtuma voisi olla esimerkiksi hiiren napsautus; se rekisteröidään tapahtumaksi onclick. Jos hiiren napsautus tapahtuisi esimerkiksi kuvan päällä, tapahtumankäsittelijä voitaisiin yhdistää <img>-elementtiin seuraavasti

<img href="kuvan osoite" onclick="kasittelijaFunktio(parametrit)">

Alla on lueteltu onclick-tapahtuman lisäksi muutamia yleisimpiä tapahtumia, mutta tämä lista ei ole täydellinen. Täydellisemmän tapahtumaluettelon löydät esimerkiksi osoiteesta HTML 5 Event Handler Content Attributes

Ellei muuta mainita, seuraavat tapahtumankäsittelijät on mahdollista liittää mihin tahansa HTML-elementtiin, tai DOM-oliohin document ja window.

Attribuutti Kuvaus
onchange Poistutaan elementistä, jota on muokattu.
onclick Elementtiä napsautettu.
ondblclick Elementtiä kaksoisnapsautettu
ondrag Raahataan elementtiä.
ondragend Raahaus lopetettiin.
ondragenter Raahattava elementti tiputettiin kohteeseen.
ondragleave Tapahtuu, jos elementti raahataan pois validin kohteen päältä.
ondragover Tapahtuu kun elementti raahataan validin kohteen päälle.
ondragstart Tapahtuu, kun raahaus alkaa.
ondrop Elementii tiputetaan raahauksen jälkeen.
ofocus Elementti aktivoitu (vain document ja body)
oninput Syötteen käsittelijä
onkeydown Näppäimen alas painaminen
onkeypress Näppäimen painallus (alas painaminen ja vapautus)
onkeyup Näppäimen vapautus
onload Lataus tehty (vain document ja body)
onmousemove Kursori liikahti elementin päällä ollessaan
onmousedown Kursori siirtyi elementin päälle ja hiiren painike painettiin alas.
onmousemove Kursori liikahti elementin päällä ollessaan
onmouseout Kursori poistui elementistä
onmouseover Kursori tuotiin elementin päälle (ongelmallinen kosketusnäytöissä)
onmouseup Hiiren painike vapautettiin elementin päällä/td>
onmousewheel Hiiren rullaa käytettiin
onscroll Vieritys tapahtunut (vain document ja body)
onsubmit Lomakkeen "submit"-painikkeen painallus.

Yllä kuvattu tapa yhdistää tapahtumat tapahtumankäsittelijöihin on se perinteinen ja yksinkertaisin tapa joka on ollut käytössä JavaScriptin alkuajoista lähtien.

Nyttemmin JavaScriptin tapahtumankäsittelyyn on olemassa monipuolempiakin tapoja. Niitä ei kuitenkaan ole tämän kurssin puitteissa mahdollista käsitellä, aiheesta lähemmin kiinnostueita kehoitetaan lukemaan seuraavat Peter-Paul Kochin artikkelit:

Takaisin ylös

5.9 JavaScript ja DOM

JavaScript ja DOM ansaitsisi suuremmankin osuuden, mutta tämän kurssin puitteissa on määritelty käytävän vain joitain perusteita. Aiheesta on kuitenkin runsaasti materiaalia saatavilla, joten tietämyksen laajentaminen on helppoa.

Olemme aiemmin kirjoittaneet lyhyitä JavaScript -ohjelmanpätkiä, jotka käsittelevät muuttujia ja kirjoittavat suoraan dokumentiin. Usein haluamme kuitenkin käyttää JavaScriptiä muuttaaksemme dokumentin DOM:a. JavaScript tarjoaa tähän metodeja mm. document-objektin kautta.

Document-objekti tarjoaa metodeja elementin ja elementtilistan (NodeList) hakemiseen. Tähän voidaan käyttää document.getElementsByTagName, tai document.querySelectorAll saadaksesi NodeList -listan elementeistä, jotka vastaavat hakua. Halutessasi vain yhden elementin, voit käyttää document.getElementById tai document.querySelector -metodeja. QuerySelector -metodit vastaanottavat CSS:tä tuttua notaatiota, mutta getElementsByTagName on laskennallisesti nopeampi, kuin querySelectorAll ja samoin getElementById on nopeampi, kuin querySelector.

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Valintaesimerkki</title>
</head>
<body>
	<p>Tämä on kappale 1</p>
	<p>Tämä on kappale 2</p>
	<p>Tämä on kappale 3</p>
	<p>Tämä on kappale 4</p>
	<div>
		<p id="valittava">Tämä on kappale 5 divin sisällä</p>
	</div>

	<script src="Script.js"></script>
</body>
</html>

Koitetaan etsiä yllä olevasta dokumentista elementtejä JavaScriptillä.


(function ()
{
	// Usean elementin valinta
	var pElementit = document.getElementsByTagName("p"); // NodeList
	var pElementit2 = document.querySelectorAll("p"); // Toinen tapa

	alert(pElementit.length); // 5

	alert(pElementit[0]); // [object HTMLParagraphElement]

	// Yksittäisen elementin valinta
	var pElementti = document.getElementById("valittava"); // [object HTMLParagraphElement]
	var pElementti2 = document.querySelector("#valittava"); // Toinen tapa

	if (pElementti !== null)
	{
		alert(pElementti.textContent); // Tämä on kappale 5 divin sisällä
	}


})();

Tässä esimerkissä valitsimme elementtejä tagName:lla ja yksittäisen elementin id:llä. Tarkistin lopussa, onko pElementti -muuttujalla arvoa, sillä jos kyseistä id:tä ei olisi löytynyt dokumentista, olisi muuttujalla ollut arvona null, eikä siitä olisi voinut kutsua textContent:ia.

Koska querySelector ja querySelectorAll vastaanottavat CSS -kyselyjä, voidaan niillä etsiä elementtejä melko tehokkaasti ja tutun oloisesti. Esimerkin dokumentissa oltaisiin voitu jättää pois id="valittava" p -elementiltä ja kirjoittaa kysely muotoon var pElementti = document.querySelectorAll("div p"); koska vain yksi p -elementeistä oli divin sisällä.

Elementtien luominen ja DOM -muokkaukset

Aiemmin olemme jo käyttäneet document.write -metodia, mutta useimmiten haluamme luoda sivulle DOM-elementtejä suoraan kirjoittamisen sijaan. JavaScript ja tarkemmin document-objekti tarjoaa metodit elementtien luomiseen sivulle. Tässä on kuitenkin tärkeää huomata, että JavaScriptin suorituksen aikana luodut uudet elementit eivät tietenkään kirjoitu palvelimella olevaan dokumentiin, vaan muutokset ovat näkyvissä vain sivun selaimessaolon ajan. Halutessaan nämä muutokset voidaan tallentaa paikallisesti localStorage -objektiin, mutta tällöinkin ne ovat vain kyseisen paikallisen asiakkaan käytössä.

Jatketaan aiemman HTML-dokumentin kanssa ja muutetaan JavaScriptiä.


(function ()
{
	var el = document.createElement("p");
	var sisalto = document.createTextNode("Tämä on dynaamisesti luotu kappale 6");

	// Liitetään sisältö uudelle kappaleelle
	el.appendChild(sisalto); 

	// Liitetään uusi kappale, ei suoraan dokumenttiin, vaan document.bodyyn
	document.body.appendChild(el);

})();

DOM-puun läpikäyminen on laskennallisesti kallis operaatio. Joka kerran, kun kutsutaan getElementById tai vastaavaa metodia joudutaan käymään DOM-puuta läpi. Jos viitataan samaan elementiin useammin kuin kerran, tulee laskennallisesti halvemmaksi tallettaa viittaus muuttujaan. Esimerkiksi var pElementti = document.getElementById("valittava"); hakee kyseisen elementin ja tallentaa siihen viittauksen muuttujaan. Jatkossa käytämme vain viittausta pElementti, emmekä hae elementtiä uudelleen DOM-puusta. Samaten muuttujan hakeminen toiselta tasolta on ylimääräistä laskemista. Kuten aiemmin oli puhetta, muuttujaa etsitään ensin siltä tasolta, jolta sitä kutsuttiin ja sitten ulommilta tasoilta. Yllä olevassa esimerkissä document-objektia kutsutaan kolmasti ja joka kerta sitä joudutaan hakemaan ensin samalta tasolta ja sitten ulommalta tasolta. Jos funktion alussa tallennettaisiin viittaus var dokumentti = document; ja sitten käytettäisiin tätä viittausta kaikissa myöhemmissä kohdissa, säästettäisii vähän laskemisesta. Ero on todella pieni, mutta isommissa projekteissa suoritusajatkin kannattaa ottaa huomioon.

Yllä olevassa esimerkissä kannattaa myös huomata document.createTextNode -metodin käyttö. Kyseinen metodi luo nimensä mukaisesti tekstisolmun, joka voi sisältää vain tekstiä. Jos sen sisällä oltaisiin kirjoitettu <strong>Kappale 6</strong> se olisi tullut selaimeenkin vain tekstinä. Jos sen sijaan halutaan antaa elementille myös HTML -muotoista sisältöä, voidaan käyttää innerHTML:ää kirjoittamalla el.innerHTML = "<strong>Kappale 6</strong>.

Takaisin ylös