b

Javascriptiä

Kurssin aikana on websovelluskehityksen rinnalla tavoite ja tarve oppia riittävässä määrin Javascriptiä.

Javascript on kehittynyt viime vuosina nopeaan tahtiin, ja käytämme kurssilla kielen uusimpien versioiden piirteitä. Javascript-standardin virallinen nimi on ECMAScript. Tämän hetken tuorein versio on kesäkuussa 2019 julkaistu ES10, toiselta nimeltään ECMAScript 2019.

Selaimet eivät vielä osaa kaikkia Javascriptin uusimpien versioiden ominaisuuksia. Tämän takia selaimessa suoritetaan useimmiten koodia joka on käännetty (englanniksi transpiled) uudemmasta Javascriptin versiosta johonkin vanhempaan, laajemmin tuettuun versioon.

Tällä hetkellä johtava tapa tehdä transpilointi on Babel. Create-react-app:in avulla luoduissa React-sovelluksissa on valmiiksi konfiguroitu automaattinen transpilaus. Katsomme kurssin osassa 7 tarkemmin miten transpiloinnin konfigurointi tapahtuu.

Node.js on melkein missä vaan, mm. palvelimilla toimiva, Googlen chrome V8-javascript-moottoriin perustuva Javascript-suoritusympäristö. Harjoitellaan hieman Javascriptiä Nodella. Tässä oletetaan, että koneellasi on Node.js:stä vähintään versio 10.18.0. Noden tuoreet versiot osaavat suoraan Javascriptin kohtuullisen uusia versioita, joten koodin transpilaus ei ole tarpeen.

Koodi kirjoitetaan .js-päätteiseen tiedostoon, ja suoritetaan komennolla node tiedosto.js

Koodia on mahdollisuus kirjoittaa myös Node.js-konsoliin, joka aukeaa kun kirjoitat komentorivillä node tai myös selaimen developer toolin konsoliin. Chromen uusimmat versiot osaavat suoraan transpiloimatta melko hyvin Javascriptin uusiakin piirteitä.

Javascript muistuttaa nimensä ja syntaksinsa puolesta läheisesti Javaa. Perusmekanismeiltaan kielet kuitenkin poikkeavat radikaalisti. Java-taustalta tultaessa Javascriptin käyttäytyminen saattaa aiheuttaa hämmennystä, varsinkin jos kielen piirteistä ei viitsitä ottaa selvää.

Tietyissä piireissä on myös ollut suosittua yrittää "simuloida" Javascriptilla eräitä Javan piirteitä ja ohjelmointitapoja. En suosittele.

Muuttujat

Javascriptissä on muutama tapa määritellä muuttujia:

const x = 1
let y = 5

console.log(x, y)   // tulostuu 1, 5
y += 10
console.log(x, y)   // tulostuu 1, 15
y = 'teksti'
console.log(x, y)   // tulostuu 1, teksti
x = 4               // aiheuttaa virheen

const ei oikeastaan määrittele muuttujaa vaan vakion, jonka arvoa ei voi enää muuttaa. let taas määrittelee normaalin muuttujan.

Esimerkistä näemme myös, että muuttujan tallettaman tiedon tyyppi voi vaihtua suorituksen aikana, y tallettaa aluksi luvun ja lopulta merkkijonon.

Javascriptissa on myös mahdollista määritellä muuttujia avainsanan var avulla. Var oli pitkään ainoa tapa muuttujien määrittelyyn, const ja let tulivat kieleen mukaan vasta versiossa ES6. Var toimii tietyissä tilanteissa eri tavalla kuin useimpien muiden kielien muuttujien määrittely. Tällä kurssilla varin käyttö ei ole suositeltavaa eli käytä aina const:ia tai let:iä!

Lisää aiheesta esim. youtubessa var, let and const - ES6 JavaScript Features

Taulukot

Taulukko ja muutama esimerkki sen käytöstä

const t = [1, -1, 3]

t.push(5)

console.log(t.length) // tulostuu 4
console.log(t[1])     // tulostuu -1

t.forEach(value => {
  console.log(value)  // tulostuu 1, -1, 3, 5 omille riveilleen
})                    

Huomattavaa esimerkissä on se, että taulukon sisältöä voi muuttaa, vaikka se on määritelty const:ksi. Koska taulukko on olio, viittaa muuttuja koko ajan samaan olioon, jonka sisältö muuttuu sitä mukaa kuin taulukkoon lisätään uusia alkioita.

Eräs tapa käydä taulukon alkiot läpi on esimerkissä käytetty forEach, joka saa parametrikseen nuolisyntaksilla määritellyn funktion

value => {
  console.log(value)
}

forEach kutsuu funktiota jokaiselle taulukon alkiolle, antaen taulukon yksittäisen alkion yksi kerrallaan funktiolle parametrina. forEachin parametrina oleva funktio voi saada myös muita parametreja.

Edellisessä esimerkissä taulukkoon lisättiin uusi alkio metodilla push. Reactin yhteydessä sovelletaan usein funktionaalisen ohjelmoinnin tekniikoita, jonka eräs piirre on käyttää muuttumattomia (engl. immutable) tietorakenteita. React-koodissa kannattaakin mieluummin käyttää metodia concat, joka ei lisää alkiota taulukkoon vaan luo uuden taulukon, jossa on lisättävä alkio sekä vanhan taulukon sisältö:

const t = [1, -1, 3]

const t2 = t.concat(5)

console.log(t)  // tulostuu [1, -1, 3]
console.log(t2) // tulostuu [1, -1, 3, 5]

Metodikutsu t.concat(5) ei siis lisää uutta alkiota vanhaan taulukkoon, vaan palauttaa uuden taulukon, joka sisältää vanhan taulukon alkioiden lisäksi uuden alkion.

Taulukoille on määritelty runsaasti hyödyllisiä operaatioita. Katsotaan pieni esimerkki metodin map käytöstä.

const t = [1, 2, 3]

const m1 = t.map(value => value * 2)
console.log(m1)   // tulostuu [2, 4, 6]

Map muodostaa taulukon perusteella uuden taulukon, jonka jokainen alkio luodaan map:in parametrina olevan funktion avulla, esimerkin tapauksessa kertomalla alkuperäinen luku kahdella.

Map voi muuttaa taulukon myös täysin erilaiseen muotoon:

const m2 = t.map(value => '<li>' + value + '</li>')
console.log(m2)  
// tulostuu [ '<li>1</li>', '<li>2</li>', '<li>3</li>' ]

Eli lukuja sisältävästä taulukosta tehdään map-metodin avulla HTML-koodia sisältävä taulukko. Tulemmekin kurssin osassa2 näkemään, että mapia käytetään Reactissa todella usein.

Taulukon yksittäisiä alkioita on helppo sijoittaa muuttujiin destrukturoivan sijoituslauseen avulla:

const t = [1, 2, 3, 4, 5]

const [first, second, ...rest] = t

console.log(first, second)  // tulostuu 1, 2
console.log(rest)          // tulostuu [3, 4 ,5]

Eli muuttujiin first ja second tulee sijoituksen ansiosta taulukon kaksi ensimmäistä lukua. Muuttujaan rest "kerätään" sijoituksesta jäljelle jääneet luvut omaksi taulukoksi.

Oliot

Javascriptissä on muutama tapa määritellä olioita. Erittäin yleisesti käytetään olioliteraaleja, eli määritellään olio luettelemalla sen kentät (englanniksi property) aaltosulkeiden sisällä:

const object1 = {
  name: 'Arto Hellas',
  age: 35,
  education: 'Filosofian tohtori',
}

const object12 = {
  name: 'Full Stack -websovelluskehitys',
  level: 'aineopinto',
  size: 5,
}

const object3 = {
  name: {
    first: 'Juha',
    last: 'Tauriainen',
  },
  grades: [2, 3, 5, 3],
  department: 'TKTL',
}

Kenttien arvot voivat olla tyypiltään mitä vaan, lukuja, merkkijonoja, taulukoita, olioita...

Olioiden kenttiin viitataan pistenotaatiolla, tai hakasulkeilla:

console.log(object1.name)         // tulostuu Arto Hellas
const fieldName = 'age' 
console.log(object1[fieldName])    // tulostuu 35

Olioille voidaan lisätä kenttiä myös lennossa joko pistenotaation tai hakasulkeiden avulla:

object1.address = 'Tapiola'
object1['secret number'] = 12341

Jälkimmäinen lisäyksistä on pakko tehdä hakasulkeiden avulla, sillä pistenotaatiota käytettäessä secret number ei kelpaa kentän nimeksi.

Javascriptissä olioilla voi luonnollisesti olla myös metodeja. Emme kuitenkaan tarvitse tällä kurssilla ollenkaan itse määriteltyjä metodillisia olioita, joten asiaa ei tällä kurssilla käsitellä kuin lyhyesti.

Olioita on myös mahdollista määritellä ns. konstruktorifunktioiden avulla, jolloin saadaan aikaan hieman monien ohjelmointikielten, esim. Javan luokkia (class) muistuttava mekanismi. Javascriptissä ei kuitenkaan ole luokkia samassa mielessä kuin olio-ohjelmointikielissä. Kieleen on kuitenkin lisätty versiosta ES6 alkaen luokkasyntaksi, joka helpottaa tietyissä tilanteissa olio-ohjelmointikielimäisten luokkien esittämistä.

Funktiot

Olemme jo tutustuneet ns. nuolifunktioiden määrittelyyn. Täydellinen eli "pitkän kaavan" mukaan menevä tapa nuolifunktion määrittelyyn on seuraava

const sum = (p1, p2) => {
  console.log(p1)
  console.log(p2)
  return p1 + p2
}

ja funktiota kutsutaan kuten olettaa saattaa

const result = sum(1, 5)
console.log(result)

Jos parametreja on vain yksi, voidaan sulut jättää määrittelystä pois:

const square = p => {
  console.log(p)
  return p * p
}

Jos funktio sisältää ainoastaan yhden lausekkeen, ei aaltosulkeita tarvita. Tällöin funktio palauttaa ainoan lausekkeensa arvon. Eli jos poistetaan konsoliin tulostus, voidaan edellinen funktio ilmaista lyhyemmin seuraavasti:

const square = p => p * p

Tämä muoto on erityisen kätevä käsiteltäessä taulukkoja esim. map-metodin avulla:

const t = [1, 2, 3]
const tSquared = t.map(p => p * p)
// tSquared on nyt [1, 4, 9]

Nuolifunktio on tullut Javascriptiin vasta muutama vuosi sitten version ES6 myötä. Tätä ennen ainoa tapa funktioiden määrittelyyn oli avainsanan function käyttö.

Määrittelytapoja on kaksi, funktiolle voidaan antaa function declaration -tyyppisessä määrittelyssä nimi, jonka avulla funktioon voidaan viitata:

function product(a, b) {
  return a * b
}

const vastaus = product(2, 6)

Toinen tapa on tehdä määrittely funktiolausekkeena. Tällöin funktiolle ei tarvitse antaa nimeä ja määrittely voi sijaita muun koodin seassa:

const average = function(a, b) {
  return (a + b) / 2
}

const vastaus = average(2, 5)

Määrittelemme tällä kurssilla kaikki funktiot nuolisyntaksin avulla.

Olioiden metodit ja this

Koska käytämme tällä kurssilla Reactin hookit sisältävää versiota, meidän ei kurssin aikana tarvitse määritellä ollenkaan olioita, joilla on metodeja. Tämän luvun asiat siis eivät ole kurssin kannalta relevantteja, mutta varmasti monella tapaa hyödyllisiä tietää. Käytettäessä "vanhempaa Reactia", tämän luvun asiat on hallittava.

Nuolifunktiot ja avainsanan function avulla määritellyt funktiot poikkeavat radikaalisti siitä miten ne käyttäytyvät olioon itseensä viittaavan avainsanan this suhteen.

Voimme liittää oliolle metodeja määrittelemällä niille kenttiä, jotka ovat funktioita:

const arto = {
  name: 'Arto Hellas',
  age: 35,
  education: 'Filosofian tohtori',
  greet: function() {
    console.log('hello, my name is', this.name)
  },
}

arto.greet()  // tulostuu hello, my name is Arto Hellas

Metodeja voidaan liittää olioille myös niiden luomisen jälkeen:

const arto = {
  name: 'Arto Hellas',
  age: 35,
  education: 'Filosofian tohtori',
  greet: function() {
    console.log('hello, my name is', this.name)
  },
}

arto.growOlder = function() {  this.age += 1}
console.log(arto.age)   // tulostuu 35
arto.growOlder()
console.log(arto.age)   // tulostuu 36

Muutetaan oliota hiukan:

const arto = {
  name: 'Arto Hellas',
  age: 35,
  education: 'Filosofian tohtori',
  greet: function() {
    console.log('hello, my name is', this.name)
  },
  doAddition: function(a, b) {    console.log(a + b)  },}

arto.doAddition(1, 4)        // tulostuu 5

const referenceToAddition = arto.doAddition
referenceToAddition(10, 15)  // tulostuu 25

Oliolla on nyt metodi doAddition, joka osaa laskea parametrina annettujen lukujen summan. Metodia voidaan kutsua normaaliin tapaan olion kautta arto.doAddition(1, 4) tai tallettamalla metodiviite muuttujaan ja kutsumalla metodia muuttujan kautta referenceToAddition(10, 15).

Jos yritämme samaa metodille greet, aiheutuu ongelmia:

arto.greet()       // tulostuu hello, my name is Arto Hellas

const referenceToGreet = arto.greet
referenceToGreet() // tulostuu ainoastaan hello, my name is

Kutsuttaessa metodia viitteen kautta, on metodi kadottanut tiedon siitä mikä oli alkuperäinen this. Toisin kuin melkein kaikissa muissa kielissä, Javascriptissa this:n arvo määrittyy sen mukaan miten metodia on kutsuttu. Kutsuttaessa metodia viitteen kautta, this:in arvoksi tulee ns. globaali objekti ja lopputulos ei ole yleensä ollenkaan se, mitä sovelluskehittäjä olettaa.

This:in kadottaminen aiheuttaa Javascriptillä ohjelmoidessa monia potentiaalisia ongelmia. Eteen tulee erittäin usein tilanteita, missä Reactin/Noden (oikeammin ilmaistuna selaimen Javascript-moottorin) tulee kutsua joitain ohjelmoijan määrittelemien olioiden metodeja. Tällä kurssilla kuitenkin säästymme näiltä ongelmilta, sillä käytämme ainoastaan "thissitöntä" Javascriptia.

Eräs this:in katoamiseen johtava tilanne tulee esim. jos pyydetään Artoa tervehtimään sekunnin kuluttua metodia setTimeout hyväksikäyttäen.

const arto = {
  name: 'Arto Hellas',
  greet: function() {
    console.log('hello, my name is', this.name)
  },
}

setTimeout(arto.greet, 1000)// sekunnin päästä tulostuu hello, my name is

Javascriptissa this:in arvo siis määräytyy siitä miten metodia on kutsuttu. setTimeoutia käytettäessä metodia kutsuu Javascript-moottori ja this viittaa Timeout-olioon.

On useita mekanismeja, joiden avulla alkuperäinen this voidaan säilyttää, eräs näistä on metodin bind käyttö:

setTimeout(arto.greet.bind(arto), 1000)
// sekunnin päästä tulostuu hello, my name is Arto Hellas

Komento arto.greet.bind(arto) luo uuden funktion, missä se on sitonut this:in tarkoittamaan Artoa riippumatta siitä missä ja miten metodia kutsutaan.

Nuolifunktioiden avulla on mahdollista ratkaista eräitä this:iin liittyviä ongelmia. Olioiden metodeina niitä ei kuitenkaan kannata käyttää, sillä silloin this ei toimi ollenkaan.

Jos haluat ymmärtää paremmin javascriptin this:in toimintaa, löytyy internetistä runsaasti materiaalia aiheesta. Esim. egghead.io:n 20 minuutin screencastsarja Understand JavaScript's this Keyword in Depth on erittäin suositeltava!

Luokat

Kuten aiemmin mainittiin, Javascriptissä ei ole olemassa olio-ohjelmointikielten luokkamekanismia. Javascriptissa on kuitenkin ominaisuuksia, jotka mahdollistavat olio-ohjelmoinnin luokkien "simuloinnin". Emme mene nyt sen tarkemmin Javascriptin olioiden taustalla olevaan prototyyppiperintämekanismiin.

Tutustutaan nyt pikaisesti ES6:n myötä Javascriptiin tulleeseen luokkasyntaksiin, joka helpottaa oleellisesti luokkien (tai luokan kaltaisten asioiden) määrittelyä Javascriptissa.

Seuraavassa on määritelty "luokka" Person ja sille kaksi Person-oliota:

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  greet() {
    console.log('hello, my name is', this.name)
  }
}

const arto = new Person('Arto Hellas', 35)
arto.greet()

const juhq = new Person('Juha Tauriainen', 48)
juhq.greet()

Syntaksin osalta luokat ja niistä luodut oliot muistuttavat erittäin paljon esim. Javan luokkia ja olioita. Käyttäytymiseltäänkin ne ovat aika lähellä Javan olioita. Perimmiltään kyseessä on kuitenkin edelleen Javascriptin prototyyppiperintään perustuvista olioista. Molempien olioiden todellinen tyyppi on Object sillä Javascriptissä ei perimmiltään ole muita tyyppejä kuin Boolean, Null, Undefined, Number, String, Symbol ja Object

Luokkasyntaksin tuominen Javascriptiin on osin kiistelty lisäys, ks. esim. Not Awesome: ES6 Classes tai Is “Class” In ES6 The New “Bad” Part?

ES6:n luokkasyntaksia käytetään paljon "vanhassa" Reactissa ja Node.js:ssä ja siksi sen tunteminen on tälläkin kurssilla paikallaan. Koska käytämme kurssilla Reactin uutta hook-ominaisuutta, meidän ei ole tarvetta käyttää kurssilla ollenkaan Javascriptin luokkasyntaksia.

Javascript-materiaalia

Javascriptistä löytyy verkosta suuret määrät sekä hyvää että huonoa materiaalia. Tällä sivulla lähes kaikki Javascriptin ominaisuuksia käsittelevät linkit ovat Mozillan Javascript -materiaaliin.

Mozillan sivuilta kannattaa lukea oikeastaan välittömästi A re-introduction to JavaScript (JS tutorial).

Jos haluat tutustua todella syvällisesti Javascriptiin, löytyy internetistä ilmaiseksi mainio kirjasarja You-Dont-Know-JS.

Toinen hieno resurssi Javascriptin oppimiseen on [javascript.info] (https://javascript.info).

egghead.io:lla on tarjolla runsaasti laadukkaita screencasteja Javascriptista, Reactista ym. kiinnostavasta. Valitettavasti materiaali on osittain maksullista.