Skip to content
Tällä sivulla

javaScript

Koodiesimerkkien suorittaminen

Voit suorittaa esimerkkikoodia haluamassasi ympäristössä.

Mm. tämä online työkalu toimii hyvin esimerkkien ajamisessa:

https://www.sololearn.com/compiler-playground/javascript

Yleistä

  • Historiasta, alkuun ollut kieli jolla voinut tehdä nettisivuille vain pieniä toiminnallisuuksia, "lelukieli".
  • Nykyään mahdollista suorittaa melkein millä tahansa alustalla (Node.js, Electron työpöytäsovelluksille, React Native, espruino microcontrollereille... jne.)
  • Ainoa ohjelmointikieli jolla on natiivi rajapinta selaimen document-objektiin (DOM), tästä syystä tehokkain kieli tehdä SPA-sovelluksia
  • EcmaScript on javaScriptin standardi, kun puhutaan ES-versioista tarkoitetaan sillä javaScriptin EcmaScript standardin versioita. (JavaScript == EcmaScript)
  • Merkittävin muutos javaScriptiin on tapahtunut ES6 version käyttöönotossa vuonna 2015.
  • Tulkattu (JIT), "single threaded" ohjelmointikieli.
  • Tarvii suoritukseen JavaScript moottorin, esim. Chromen V8. Nämä selainten JS-moottorit on vuosien saatossa äärimmilleen optimoitu, jotta esim. mobiililaitteilla saataisiin koodi suoritettu mahdollisimman energiatehokkaasti. Tämän takia javaScript on verrattain energiatehokas tulkattu ohjelmointikieli.

Hyvä dokumentaatio modernille (ES6+) JavaScriptille, jota opintojaksolla käytetään: https://javascript.info/

Käydään kertauksessa läpi perusasioita ja toiminnallisuuksia joita tullaan myöhemmin kurssilla hyödyntämään

Muuttujat

JavaScriptin muuttujien kertausta. Hyvä ohjenuora siihen että kumpaa käyttää let vai const on se että käytä let esittelyä vain kun haluat esitellä muuttujan johon tiedät asettavasi uuden arvon myöhemmin koodissa.

JavaScriptissä { } sulkeet muodostavat blokin, se voi olla esimerkiksi for-loop tai ehtolauseke.

ES6+ syntaksissa käytetään blokkitason muuttujia (block scope variables) let ja const.

Muuttujat esitellään aina ennen kuin niitä voidaan käyttää, blokissa esitelty muuttuja ei ole käytettävissä blokin ulkopuolella.

js

let string1 = 'tähän muuttujaan voi asettaa arvon uudelleen'
const string2 = 'tähän muuttujaan ei voi asettaa uutta arvoa'

let object1 = { key_one: 'tähän objekti literaaliin "object1" voi asettaa uuden arvon' }
const object2 = { just_a_key: 'tähän ei, mutta objektin sisällä arvot voivat silti muuttua, kuten tämä string' }

let array1 = ['tähän "array1" muuttujaan voi asettaa uuden arvon']
const array2 = ['tähän ei, mutta myös const arrayn sisällä arvot voidaan asettaa uudelleen']

// Jos yrität alustaa const muuttujan uudelleen saat vastaukseksi:
string2 = 'uusi'                    // TypeError: Assignment to constant variable.
array2 = []                         // TypeError: Assignment to constant variable.
object2 = {other_key: 'ei onnistu'} // TypeError: Assignment to constant variable.

Huom!

"const" estää muuttujaan arvon asettamisen uudelleen, const muuttujaan asetetun objektin tai arrayn sisältöä voi kuitenkin muokata

var

Älä käytä muuttujien esittelyssä var avainsanaa, var on eläköitynyt ES5 version jälkeen eikä sitä tule käyttää sekaisin ES6 tai uudemman EcmaScript version kanssa

String

JavaScriptissä merkkijonon eli stringin käsittelyyn on olemassa lukuisia eri tapoja. Käydään esimerkkien kautta muutamia yleisimpiä tapauksia läpi.

Merkkijono voidaan asettaa muuttujaan esimerkiksi seuraavilla tavoilla:

js

const merkkijonoYksi = "Tämä stringi on luotu käyttämällä lainausmerkkejä"
const merkkijonoKaksi = 'Tämä stringi on luotu käyttämällä heittomerkkejä eli "hipsuja"'

const numero = 123
const merkkijonoKolme = numero.toString() + "\
numerot muutetaan stringiksi käyttämällä .toString() metodia \
joka löytyy lähes jokaiselta eri javaScriptin tietotyypiltä. \
Tämän jälkeen '123' string yhdistetään käyttämällä + operaattoria \
toiseen stringiin."


const templateMerkkijono = `

    Tämä on template stringi ja tällä on erityisiä ominaisuuksia.

    TemplateStringi säilyttää sille annetun muotonsa täydentämällä 
    rivinvaihdot ja muun tyhjän tilan merkit merkkijonoon automaattisesti

    Template stringiin voidaan lisätä sisään JavaScript 
    koodia käyttämällä ${ "merkintää" }. 
    
    Merkinnän sisällä oleva koodi suoritetaan ja tulos muutetaan automaattisesti 
    String tietotyypiksi. 

    Esimerkiksi ${numero + 1} asettaa stringin sisälle numeron 124.

`

Käydään läpi myös yleisimpiä String tietotyypin metodeja, eli tietotyypin sisään rakennettuja funktioita joilla voidaan suorittaa eri toimintoja tietotyypin datalle.

Stringeille käytetään usein seuraavia metodeja:

js
const str = 'Pieni kettu hyppäsi aidan yli ja juoksi metsään.'

// Tarkistetaan sisältääkö string kyseistä stringiä. 
// Vastaus voi olla joko true tai false. 
// Case-sensitive eli kirjainkoolla on merkitystä.
str.includes('kettu') // true
str.includes('Kettu') // false

// .split() metodi pilkkoo stringin annetulla argumentilla arrayksi
str.split(' ')
// Logs: [ 'Pieni', 'kettu', 'hyppäsi', 'aidan', 'yli', 'ja', 'juoksi', 'metsään.' ]

// Huomaa että .split() metodi poistaa annetun string argumentin kokonaan.
str.split('a')
// Logs: [ 'Pieni kettu hyppäsi ', 'id', 'n yli j', ' juoksi metsään.' ]

// Arrayn saa takaisin stringiksi käyttämällä arrayn .join() metodia!
str.split(' ').join(' * ')
// Logs: 'Pieni * kettu * hyppäsi * aidan * yli * ja * juoksi * metsään.'

// .replace() metodi vaihtaa ensimmäisen löytämänsä matchin stringistä.
// Palauttaa uuden stringin, ei muokkaa olemassa olevaa.
str.replace('kettu', 'KANI')
// Logs: 'Pieni KANI hyppäsi aidan yli ja juoksi metsään.'

// Useat string metodit ottavat vastaan Regular Expressionin argumenttina,
// tämä mahdollistaa varsin monimutkaisten string tietotyypin käsittelyn.
// Tästä linkistä lisää RegExp:stä:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp

// RegExp literaali esimerkkinä kuinka käyttää replace metodia 
// vaihtamaan kaikki löydetyt vastaavuudet.
'kettu kettu kettu kettu'.replace(/kettu/g, 'pupu')
// Logs: 'pupu pupu pupu pupu'


Muita hieman harvemmin käytettyjä tapoja käsitellä stringejä:

js
// Asetetaan perinteinen Hello World muuttujaan str
const str = 'Hello World!'

// Otetaan W kirjain käyttämällä slice metodia
const w = str.slice(6,7)

// Otetaan e kirjain käyttämällä substring metodia,
// joka on lähes sama kuin slice.
const e = str.substring(1,2)

// Otetaan huutomerkki Hello Worldin lopusta käyttämällä merkin indeksiä.
// Huom! Stringiä ei voi muokata indeksin perusteella kuten arrayta.
let huuto = str[11]

// Asetetaan uusi arvo muuttujaan huuto yhdistämällä 
// aiemmin luodut w, e ja huuto muuttujat.
// Käytetään e muuttujaan .repeat() metodia.
huuto = w + e.repeat(10) + huuto

// Tulostaa konsoliin 'WEEEEEEEEEE!'
console.log(huuto.toUpperCase())

JavaScriptin merkkijonoista lisää Mozillan String dokumentaatiosta

Samassa yhteydessä on hyvä tutustua lisää Template Literaaleihin tai toiselta nimeltä Template Stringiin

Number

JavaScriptin Number tietotyyppi lyhyesti. Lisää Mozillan dokumentaatiossa

js
// Muunnos stringistä numeroksi
Number('123.001') // 123.001

// Muunnos onnistuu vain jos string sisältää validin numeron
Number('a123')    // NaN

// Muunnos desimaaliluvusta kokonaisluvuksi
parseInt(123.5012321)   // 123

// Suositeltavaa käyttää Math.floor() tai Math.round() parseIntin sijasta
// Math.round pyöristää luvun seuraavaan kokonaislukuun kun taas
// parseInt katkaisee luvun desimaalipisteen kohdalta.
Math.round(123.5012321) // 124

// Esimerkki numeron poimimiseksi stringistä
const str = 'count: 101 pieces'
const arr = str.split(' ')   // [ 'count:', '101', 'pieces' ]
console.log(Number(arr[1]))  // 101

// Ole aina varma siitä että laskettavien arvojen tietotyyppi on Number
// Muuten voi käydä näin:
let numStr = '2'
let num = 1

// JavaScript olettaa että haluat tehdä string concatenation ja muuttaa numeron
// stringiksi, kun yhteenlaskun '+' operaattori on myös 
// string concatenation operaattori.
console.log(num + numStr)         // '12'
console.log(num + Number(numStr)) // 3

// Math on JavaScriptin sisäänrakennettu kirjasto matemaattisille operaatioille

// Palauttaa satunnaisen desimaaliluvun väliltä 0-1
Math.random() // esim. 0.5220912584074033

// Satunnainen kokonaisluku väliltä 128-255
Math.round(Math.random() * (255 - 128) + 128)

// Esitystapoja

// Suurien lukujen esittäminen koodissa
1000000000 === 1E9 // true
1000_000_000 === 1E9 // true

// Hexadesimaali
0xFF === 255 // true

// Binääridatan esitys
0b11111111 === 255 // true

// Aritmeettiset operaatiot noudattavat
// normaaleja laskujärjestyssääntöjä.
let radius = 10 + (10 / 2) * (2.6 - 0.345) // 21.275

// Suurin osa matemaattisista operaatioista ja vakioista löytyvät
// sisäänrakennetusta Math kirjastosta.
// ** on shorthand Math.pow() operaatiolle, eli potenssilaskulle
let circleArea = Math.PI * radius ** 2 // 1421.9653383264886

Boolean

Lisää Boolean tietotyypistä Mozillan dokumentaatiossa

js

// Boolean muuttujat on suositeltavaa nimetä tavalla 
// jolla siitä pystyy nimeämisen perusteella kertomaan
// että kyseessä on joko true tai false.
// Esim. hasData, isSet, canUpdate jne.
// Boolean muuttujilla on myös hyvä olla aina oletusarvo asetettuna.
let isSet = false

if(isSet) {
  console.log("isSet on true")
} else {
  console.log("isSet on false")
}

// Truthy ja falsy data.
// JavaScriptissä vertailuoperaattorit voivat verrata yhtäsuuruutta joko truthyn tai falsyn 
// datan perusteella käyttämällä '==' operaattoria tai sitten niin että myös tietotyyppi on otettu 
// vertailussa huomioon käyttämällä '===' operaattoria.

// Käydään läpi esimerkein kuinka selvitään vertailuviidakosta.

// "Löysä" vertailu
console.log('' == 1)            // false
console.log(null == '')         // false
console.log(false == null)      // false

console.log('' == 0)            // true
console.log('1' == 1)           // true
console.log(null == undefined)  // true
console.log(false == '0')       // true
console.log(false == 0)         // true
console.log(true == '1')        // true

// "Tiukka" vertailu
console.log('' === 1)           // false
console.log(null === '')        // false
console.log(false === null)     // false
console.log('' === 0)           // false
console.log('1' === 1)          // false
console.log(null === undefined) // false
console.log(false === '0')      // false
console.log(false === 0)        // false
console.log(true === '1')       // false

// Vertailuissa kannattaa olla tarkkana ja suosia tiukkaa vertailua,
// on kuitenkin poikkeuksia jolloin '==' vertailu operaattori on hyödyllinen.
// Kuvitellaan esimerkiksi että 'objekti' olisi dataa joka on haettu 3. osapuolen 
// REST rajapinnasta ja jos dataa ei ole niin rajapinta palauttaa tyhjän objektin.
// Tiedetään että jos objektissa on dataa niin se löytyisi avaimella 'avain'.
// Tiedetään että rajapinta on vanha ja saattaa palauttaa omituisia arvoja ja
// objektissa saattaa olla avain nimeltä 'avain' mutta sen arvo voi olla null.
// Tiedetään että jos yritetään suorittaa toimenpide olemattomalle avaimelle ohjelma kaatuu.

const objekti = {}

console.log(objekti.avain == undefined) // true
console.log(objekti.avain == null) // true

// Ennen toimenpidettä voidaan käyttää hyödyksi löysää vertailua jolla saamme varmistettua ettei
// rajapinnasta tullut data ole null tai undefined. Eli data != null tarkastaa 
// molemmat, nullin että undefinedin.
if(objekti.avain != null){
  // Jos null tai undefined pääsisi tähän, ohjelman suorittaminen
  // keskeytyisi erroriin:
  // TypeError: Cannot read properties of undefined // (tai null)
  objekti.avain.split()
}

// Yhtäsuuruutta vertailtaessa voidaan helposti vertailla erisuuruutta vaihtamalla mukaan
// huutomerkki '!=='

console.log(false !== true) // true

// Huutomerkki notaatio onkin yksi keino selvittää helposti onko kyseessä falsy vai
// truthy arvo. Yksi huutomerkki tekee antaa käänteisen booleanin eli falsy muuttuu trueksi.
// Kaksi huutomerkkiä kääntää sen todelliseen boolean arvoon.

// Tyhjä string on falsy
console.log(!"") // true
console.log(!!"") // false

// "a" string on truthy
console.log(!"a") // false
console.log(!!"a") // true

// Booleanin voi asettaa muuttujaan suoraan vertailusta, tämä on hyvä tapa 
// jos täytyy tehdä suuria määriä vertailuja, näin vältytään siltä ettei koodi näytä tältä:

if((value.x != null && value.x.y != null) || (value.y != null && value.y.x != null)){
  // koodia...
}

// Sama mutta hieman helpompi lukea?
const isXy = value.x != null && value.x.y != null
const isYx = value.y != null && value.y.x != null

if(isXy || isYx){
  // koodia...
}

Array

Array on javaScriptin lista-tietotyyppi. Samoin kuin Object, se on "Pass by Reference" tyyppinen eli jo muuttujaan asetetun arrayn siirto uuteen muuttujaan ei tee kopiota vaan kopioi vanhan muuttujan referenssin uuteen. Hyvä käytäntö onkin luoda uusi Array jos halutaan jo luodun arrayn arvo asettaa uuteen muuttujaan.

Lisää Arraystä Mozillan dokumentaatiossa

Arrayn käsittelyn esimerkkejä:

js
// Array

const arr = [100,200,300,400,500,600,700]

// Otetaan arvo arrayn ensimmäisestä indeksistä eli nollasta
console.log(arr[0]) // 1

// Jos arrayn indeksiin ei ole asetettu arvoa niin arvona on undefined
console.log(arr[9]) // undefined

// Arrayn pituus saadaan .length getterillä
console.log(arr.length) // 7

// Arrayn viimeinen asetettu indeksi on arrayn pituus - 1
console.log(arr[arr.length - 1]) // 700

// Iteraatiot

// Perinteinen for-loop
for(let i = 0; i < arr.length; i++){
  arr[i] = arr[i] / 2
}

console.log(arr) // [ 50, 100, 150, 200, 250, 300, 350 ]

// Esitellään uusi, tyhjä Array
const arr2 = []
// for of -loop
for(let item of arr){
  // Arrayn .push() metodi "puskee" olemassa olevan Arrayn loppuun uuden arvon
  arr2.push(`v: ${item}`)
}

console.log(arr2) // [ 'v: 50', 'v: 100', 'v: 150', 'v: 200', 'v: 250', 'v: 300', 'v: 350' ]

// Arrayn .pop() metodi taas ottaa viimeisen arvon pois
const valueOne = arr2.pop()
console.log(valueOne) // 'v: 350'

console.log(arr2) // [ 'v: 50', 'v: 100', 'v: 150', 'v: 200', 'v: 250', 'v: 300' ]

// Vastaavasti .shift() metodi ottaa arraysta pois ensimmäisen arvon 
// ja .unshift() metodi taas lisää uuden arvon Arrayn alkuun.

// Paljon käytettyjä Arrayn metodeja

// .map() metodi käy jokaisen indeksin arvon läpi Arraysta johon sitä 
// käytetään ja palauttaa uuden Arrayn muokkaamatta alkuperäistä.
// Tämä onkin suositeltava tapa kun halutaan lisätä tai käsitellä 
// Arrayssa olevia arvoja niin että vältytään ei-toivotuilta 
// sivuvaikutuksilta joita olemassa olevan Arrayn muokkaaminen saattaisi
// aiheuttaa.
const newArray = arr.map((item, index) => {
  return {
    value: item,
    id: index
  }
})

console.log(newArray)
/*
Logs:
[
  { value: 50, id: 0 },
  { value: 100, id: 1 },
  { value: 150, id: 2 },
  { value: 200, id: 3 },
  { value: 250, id: 4 },
  { value: 300, id: 5 },
  { value: 350, id: 6 }
]
*/

// Samaten map metodilla saadaan eristettyä arvot arraysta jossa on
// objekteja

const valuesArray = newArray.map(item => item.value)
console.log(valuesArray) // [ 50, 100, 150, 200, 250, 300, 350 ]

// .find() metodi ottaa callback funktiossa ehdon, eli kun callback funktio 
// palauttaa true, etsintä lopetataan ja find palauttaa ensimmäisen löydetyn arvon
const found = [1,2,1,2,100,200,100].find(item => item === 100)
console.log(found) // 100

// Arrayn .filter() metodi nimensä mukaisesti filteröi pois arvot jotka eivät toteuta
// callback funktiossa olevaa ehtoa. Palauttaa uuden arrayn muokkaamatta alkuperäistä. 
// Callback funktion pitää siis palauttaa joko true tai false
// jotta filter tietää että otetaanko arvo mukaan uuteen Arrayhin.
const filteredArray = newArray.filter((item) => {
  // Jos item.id ei ole 0 ja jos item.id ei ole 6 niin ehto on true,
  // muutoin palauttaa falsen ja kyseistä arvoa ei lisätä uuteen filteredArrayhin.
  return item.id !== 0 && item.id !== 6
})

console.log(filteredArray)
/*
Logs:
[
  { value: 100, id: 1 },
  { value: 150, id: 2 },
  { value: 200, id: 3 },
  { value: 250, id: 4 },
  { value: 300, id: 5 }
]
*/

// .sort() metodi järjestää arrayn suuruus järjestykseen annetuilla ehdoilla.
// Se muokkaa olemassa olevaa Arraytä eikä palauta uutta.
// Jos sortille ei määrittele callback funktiota se yrittää automaattisesti 
// päätellä mitä järjestellään. Tämä on melko virhealtista joten callback funktio
// kannattaa aina antaa sortille mukaan.
filteredArray.sort((next, current)=> {
  // Sort metodin callback funktiossa palautetaan numero.
  // Jos palautettava numero on pienempi kuin nolla, järjestetään arvot suurimmasta pienimpään
  return  current.id - next.id
  // Jos palautettava numero on suurempi kuin nolla, järjestetään arvot pienimmästä suurimpaan
  // return next.id - current.id
})

console.log(filteredArray)
/*
Logs:
[
  { value: 300, id: 5 },
  { value: 250, id: 4 },
  { value: 200, id: 3 },
  { value: 150, id: 2 },
  { value: 100, id: 1 }
]
*/

// Arrayn .reduce() metodi on todella tehokas työkalu mutta sitä
// tulee käyttää harkiten, koska sillä saa koodin todella nopeasti ei-luettavaan kuntoon.
// Reduce ei muokkaa Arrayta jolle se suoritetaan.
const reduced = filteredArray.reduce((accumulator, current, index) => {
  // Accumulator kerryttää dataa koko arrayn iteroinnin ajan, current on
  // sen hetkisen iteraation arvo
  accumulator = accumulator + current.value
  
  // accumulator palautetaan uutta iteraatiota varten
  return accumulator
}, 0) // 0 on tässä accumulatorin lähtöarvo, accumulator olla myös muuta tietotyyppiä

console.log(reduced) // 1000

// Array destructuring
const [value1, value2, value3] = [1,2,3]
console.log(value1, value2, value3) // 1 2 3

// Array destructuring on paljon käytetty esim. React Hooksien kanssa, jossa
// useState(0) palauttaa getterin ja setterin Arrayssä:
// const [count, setCount] = useState(0);

// Array ja spread operaattori.
const array1 = [1,2,3]

// Tekee uuden arrayn:
const joinedArray = [...array1, ...[4,5,6]]
console.log(joinedArray) // [ 1, 2, 3, 4, 5, 6 ]

// Nested Array ja .flat() metodi.
// Flat palauttaa uuden arrayn muokkaamatta alkuperäistä
const nestedArray = [[1,2], [3,4], [5,6]]
const flattedArray = nestedArray.flat()
console.log(flattedArray) // [ 1, 2, 3, 4, 5, 6 ]

// .reverse() metodi muuttaa arrayn järjestyksen käänteiseksi
const reversedArray = [1,2,3].reverse()
console.log(reversedArray) // [ 3, 2, 1 ]

// Arrayn .join() metodi yhdistää kaikki arvot ja palauttaa Stringin
const stringFromArray = reversedArray.join()
console.log(stringFromArray) // '3,2,1'

// .join() metodille voi antaa String tyyppisen argumentin jolla arrayn
// arvot yhdistetään stringiksi
const anotherStringFromArray = reversedArray.join('-')
console.log(anotherStringFromArray) // '3-2-1'



Function

JavaScriptin ehkä tärkein ja tehokkain ominaisuus on sen funktiot, ne mahdollistavat turhan toistamisen välttämisen tehokkaasti. Käydään läpi joitakin tapoja käyttää funktioita JavaScriptissä:

Lisää funktioista Mozillan dokumentaatiossa

js
// Perinteinen funktion esittely.
// Function declaration (hoisted) 
function double(value) {
  return value * 2
}

// Function expression
const double2 = function(value){
  return value * 2
}

// Fat arrow function
const double3 = (value) => {
  return value * 2
}

// Fat arrow funktio shorthandilla
// Kun fat arrow funktiolle ei ole määritelty blockscopea '{}' suluin
// niin return avainsanaa ei tarvita vaan funktio palauttaa
// arvon automaattisesti.
const double4 = (value) => value * 2
double4(2) // 4

// IIFE, immediately executed function
;(function(value){
  return value * 2
})(2)

// Funktio jonka parametrina otetaan vastaan toinen funktio.
// nimetään parametri vaikkapa 'olenFunktio'
const suoritanFunktiosi = (olenFunktio) => {
  const result = olenFunktio(1,2)
  return result
}

// Callback function esimerkki
const laskeYhteen = (value1, value2) => value1 + value2

suoritanFunktiosi(laskeYhteen) // 3
// Sitten argumentiksi pari anonyymiä funktiota eri toiminnallisuuksilla
suoritanFunktiosi((val1, val2) => val1 - val2) // -1
suoritanFunktiosi((val1, val2) => (val1 + val2) ** 3) // 27

// Useimmat javaScriptin natiivimetodit hyödyntävät 
// callback funktioita. Otetaan pari esimerkkiä: 

const array = [2,5,8,1,0,4,9]

// Filteröidään parittomat luvut pois arraysta:
// Filter metodi palauttaa uuden arrayn ja lisää uuteen arrayhin
// vain ne arvot jotka palauttavat arvon true callbackissä
array.filter((value) => value % 2 === 0) // [ 2, 8, 0, 4 ]

// Kerrataan hieman
const filterCallback = (value) => {
  return value % 2 === 0
}

array.filter(filterCallback) // [ 2, 8, 0, 4 ]

// Arrayn sort metodi muokkaa arrayn dataa eikä palauta uutta arrayta!
array.sort((a,b) => b - a) // [ 9, 8, 5, 4, 2, 1, 0 ]
array.sort((a,b) => a - b) // [ 0, 1, 2, 4, 5, 8, 9 ]

// Arrayn map metodi ottaa myös callback funktion parametrina
// .map() metodi käy jokaisen arvon läpi arraysta, suorittaa
// määritetyt toimenpiteet arvoille ja palautta sitten uuden arrayn
// muokkaamatta alkuperäistä.
array.map(value => value * 2) // [ 0, 2, 4, 8, 10, 16, 18 ]

// Nimetyt argumentit funktioissa toimivat hieman kuten object destructuring.
const minunFunktio = ({ param1, param2 }) => {
  console.log(param1, param2) 
}

minunFunktio({
  param1: 'eka',
  param2: 'toka'
}) 

// Rest operaattori "..." käyttää samaa syntaksia kuin spread operaattori.
// Se laitetaan aina viimeiseksi argumentiksi funktion argumentteihin ja se 
// laittaa kaikki loput argumentit 'args' Arrayhin. 
const minunFunktio2 = (...args) => {
  console.log(args) 
}

minunFunktio2(1,2,3,4,5,6,7,8,9) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

// Funktioille voi asettaa oletus-arvot argumenteille.
const minunFunktio3 = (param1 = 'tämä on oletuksena') => {
  console.log(param1) 
}

minunFunktio3()           // Logs: 'tämä on oletuksena'
minunFunktio3('tämä ei')  // Logs: 'tämä ei'

// Sama nimetyillä argumenteilla
const minunFunktio4 = ({ param1 = 'tämä on oletuksena' }) => {
  console.log(param1) 
}

minunFunktio4()                      // Logs: 'tämä on oletuksena'
minunFunktio4({ param1: 'tämä ei' }) // Logs: 'tämä ei'

Object

Objekti koostuu avain-arvo pareista, joissa avain on aina tyyppiä String. Avaimella oleva arvo taas voi olla melkein mitä tahansa tietotyyppiä. Kun objektin avaimelle on määritetty arvoksi funktio, sitä kutsutaan metodiksi.

Objekti voi olla olemassa vain yhdessä muistipaikassa kerrallaan, joten kun objektia siirrellään muuttujasta toiseen niin oikeasti siirretään vain objektin osoitetta sen oikeaan sijaintiin tietokoneen muistissa.

Eli objektit ovat "pass by reference" dataa. Samaten objektin sisällä olevat toiset objektit ovat omissa muistipaikoissaan, tämä saattaakin aiheuttaa ei-toivottuja sivuvaikutuksia ohjelman toimintaan, ellei asiaa ota huomioon koodaamisessa. Objektien kanssa onkin suositeltavaa ettei esim. funktioissa muokata parametrina saatua objektia vaan otetaan parametrina saadusta objektista tarvittava data, tehdään datalle tarvittavat operaatiot ja palautetaan uusi objekti funktiosta.

Objekteista Mozillan dokumentaatiossa

Muita esimerkkejä: https://www.javascripttutorial.net/object/javascript-merge-objects/

Käydään läpi yleisimmät tavat käsitellä objektia.

Object literal

js
const obj = {}
obj.data = 100
obj['data2'] = 200

const avain = 'data3'
obj[avain] = 300

console.log(obj) // { data: 100, data2: 200, data3: 300 }

// Entä kun halutaan dynaamisesti luoda uusi avain objektiin 
// object literaalin kanssa?
const avain2 = 'data4'

const obj2 = {
  data3: 333,
  [avain2]:400,
  data5:500,
  'data6':600,
}

console.log(obj2) // { data3: 333, data4: 400, data5: 500, data6: 600 }

// Miten yhdistetään objekteja?
// Esimerkkejä: https://www.javascripttutorial.net/object/javascript-merge-objects/
Object.assign(obj, obj2, {data6:1000}) 
/*
console.log(obj)
{
  data: 100,
  data2: 200,
  data3: 333,
  data4: 400,
  data5: 500,
  data6: 1000
}

Object.assign funktio ottaa ensimmäisenä argumenttina objektin johon 
muutokset tehdään, jos muissa objekteissa on samoja avaimia kuin aiemmissa 
niin järjestys on määräävä tekijä siinä minkä objektin 
saman niminen avain ylikirjoitetaan.

Eli jos yhdistettävillä objekteilla on saman niminen avain 
kuten tässä tapauksessa data3, niin obj.data3 (300) ylikirjoitetaan 
obj2.data3:n datalla (333).

*/

// Objektien yhdistämiseen on parempi käyttää spread operaattoria "..."
// Spread Operator nimensä mukaisesti levittää objektin auki jättäen sisällön
// vastaanotettavaksi uuteen objektiin.

const obj3 = {data7: 700}

const obj4 = {...obj, ...obj3}
/*
console.log(obj4)
{
  data: 100,
  data2: 200,
  data3: 333,
  data4: 400,
  data5: 500,
  data6: 1000,
  data7: 700
}
*/

// delete poistaa objektin koko entryn eli avain-arvo parin
delete obj4.data
delete obj4.data4
/*
console.log(obj4)
{
  data2: 200,
  data3: 333,
  data5: 500,
  data6: 1000,
  data7: 700
}
*/

// Kun objekti literaalin avaimelle asetetaan arvoksi funktio, 
// niin funktion sisältä pääsee käsiksi objektiin itseensä 
// avainsanalla 'this'.
// Huomaa että 'fat arrow' funktioissa ei ole omaa 'this' avainsanaa
// vaan 'fat arrow' funktiossa kutsuttu this 'hyppää yli' seuraavaan 
// ylempään function scopeen.
// Eli, 'obj4.logMe = () => console.log(this)' ei tulosta objektia itseään.
// vaan global scope funktion, esimerkiksi Window selaimessa.

obj4.logMe = function() {
  console.log(this)
}

// Suoritetaan objektille luotu metodi
obj4.logMe()
/*
{
  data2: 200,
  data3: 333,
  data5: 500,
  data6: 1000,
  data7: 700,
  logMe: ƒ ()
}
*/

// Kuinka asetetaan object literaaliin metodi sen luomisen yhteydessä? 
// Tässä esimerkki sum metodista:
const obj5 = {
  values: [1,2,3,4,5,6],
  // Object literal mahdollistaa shorthandin käyttämisen, jossa 'function'
  // sana voidaan jättää pois
  sum() {
    // Käytetään arrayn sisällä olevien lukujen yhteen laskemiseksi
    // Arrayn reduce metodia.
    // Reduce metodi ottaa callback funktion jossa argumentteina ovat 
    // accumulator eli kerryttäjä sekä current eli sen hetkisen iteraation
    // arvo kyseiselle indeksille.
    return this.values.reduce((accumulator, current) => accumulator += current)
  }
}

obj5.sum() // 21

// JavaScript objekti on yleensä se data joka muutetaan JSON string formaattiin 
// esimerkiksi rajapintakyselyä varten.
// JSON on JavaScriptiin sisäänrakennettu työkalu datan muuttamiseksi json 
// stringiksi

// Kuten huomataan niin JSON.stringify() ei ota huomioon 
// objektissa olevia funktioita.
const jsonStr = JSON.stringify(obj5) // '{"values":[1,2,3,4,5,6]}'

// JSON string takaisin javaScript objektiksi:
const obj6 = JSON.parse('{"values":[1,2,3,4,5,6]}')
obj6.values // [ 1, 2, 3, 4, 5, 6 ]

// Datan hakeminen objektista
const obj7 = {
  data: 1,
  data2: 2,
  data3: 3,
  data4: 5,
  data5: {
    values: [234,53,645,456,54]
  }
}
// Piste notaatio tai avainsana
obj7.data2 + obj7.data3 + obj7['data4'] // 10

// Object destructuring
const { data } = obj7
console.log(data) // 1 

const { data5 } = obj7
console.log(data5) // { values: [ 234, 53, 645, 456, 54 ] }

// Käydään hieman läpi kuinka destructuring toimii.
// Objektin purkaminen suoraan muuttujaan tapahtuu objektin
// avainten nimien perusteella.
// Jos avainta ei voi esitellä uudeksi muuttujaksi, kuten tässä tapauksessa
// data on jo aiemmin esitelty const muuttujaksi, niin sille voi antaa
// vaihtoehtoisen nimen: data:data1, eli nimetään 'data' avain muuttujaksi 'data1'
const { data:data1, data2, data3 } = obj7
console.log(data1, data2, data3) // 1 2 3

// destructurin mahdollistaa myös sisäkkäisten objektin purkamisen
// sekä arrayn purkamisen indeksin perusteella.
// Mitä tässä siis tapahtuu?
// obj7:sta otetaan avain data5, joka on objekti missä on avain nimeltä
// 'values'. Otetaan values data5:n sisältä ja values arraystä
// indeksi 0,1 ja 2, jotka asetetaan muuttujiin nimillä val1, val2, val3
const { data5: { values: [val1, val2, val3] } } = obj7
console.log(val1, val2, val3) // 234 53 645

Class

JavaScript luokat ovat ns. piirrustukset objekteille, joita luokan perusteella luodaan käyttämällä 'new' avainsanaa. Kun luokan avulla luodaan uusi objekti, siitä käytetään nimitystä instanssi (instance).

js
// Tyypillisesti luokan nimi aloitetaan käytännön mukaan 
// isolla alkukirjaimella.
class Car {
  
  // Luokan constructor ottaa datan vastaan jota käytetään 
  // objektin rakentamisessa ja asettaa sen luokan jäsenmuuttujiin.
  constructor(model, made, km){
    this.model = model
    this.made = made
    this.km = km
  }
  
  // Objektin metodi, palauttaa tässä tapauksessa
  // string tyyppisen koosteen objektin datan perusteella.
  about() {
    return `My ${this.model} was made in ${this.made}, I have driven exactly ${this.km}km`
  }
}

const myCar = new Car('skoda', 2012, 120020)
const neighboursCar = new Car('bmw', 2021, 10012)


console.log(myCar.about())
// Logs: 'My skoda was made in 2012, I have driven exactly 120020km'

console.log(myCar)
/*
Logs:
Car {
  model: 'skoda',
  made: 2012,
  km: 120020,
  __proto__: { constructor: ƒ Car(), about: ƒ about() }
}
*/

Async & Promises

js
// Esimerkki Promisen käytöstä
// luodaan delay niminen funktio joka ottaa
// parametrina millisekunnit kaun se odottelee.
const delay = (ms = 2000) => {
  // Luodaan uusi Promise, joka voi joko onnistua tai epäonnistua.
  // new Promise ottaa callback funktion jossa on kaksi argumenttia,
  // resolve ja reject. Nimetään esimerkin takia ne ok:ksi ja notOk:ksi.
  // Resolve ja reject ovat callback funktioita jotka suoritetaan kun haluttu
  // asynkroninen toimi joko onnistuu tai epäonnistuu.
  return new Promise((ok, notOk) => {
    if (ms > 5000) notOk('Liian kauan minun makuun');

    setTimeout(() => {
      ok(ms / 1000 + ' sekuntia odoteltu');
    }, ms);
  });
};

delay(1234) // Promise { <pending> }
  .then((a) => console.log(a)) // '1.234 sekuntia odoteltu'
  .catch((err) => console.log(err));

delay(6000) // Promise { <pending> }
  .then((a) => console.log(a))
  .catch((err) => console.log(err)); // 'Liian kauan minun makuun'

// Yksinkertaistettu versio delay funktiosta
// Näppärä työkalu jos haluaa simuloida asynkronista operaatiota
const delay2 = new Promise((resolve, reject) => setTimeout(resolve, 2000));
delay2.then(() => console.log('done'));


// Asynkroninen funktio.
// Palauttaa aina Promisen, mahdollistaa await:n käytön 
// ja asynkronisten operaatioiden helpon ketjuttamisen.
// Async funktion sisällä koodin suorittaminen pysähtyy odottelemaan
// että await saa vastauksen ja jatkaa sitten koodin suorittamista 
// rivi riviltä
async function test() {
  try {
    const result = await delay(1111)
    console.log(result) // '1.111 sekuntia odoteltu'
    
    const result2 = await delay(1234)
    console.log(result2) // '1.234 sekuntia odoteltu'
    
    const result3 = await delay(2345)
    console.log(result3) // '2.345 sekuntia odoteltu'
    
    // Kun try-catchin sisällä on virhe niin koodin suorittaminen jatkuu
    // välittömästi catch blokissa.
    // Tässä tapauksessa virhe tuli kun Promisessa suoritettiin reject() koska
    // 5000ms asetettu raja ylittyi

    const result4 = await delay(6000)
  } catch (err) {
    console.log(err) // 'Liian kauan minun makuun'
  } finally {
    console.log("Finally blokki suoritetaan lopuksi riippumatta tuliko virhettä vai ei")
  }
}

// Suoritetaan async esimerkki funktio. Async funktio palauttaa aina Promisen vaikka 
// funktiossa ei olisi returnia ollenkaan.
const testResult = test()
console.log(testResult) // Promise { <pending> }


// Esimerkki selaimen Fetch API:n käytöstä
fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => response.json())
  .then((data) => console.log(data));
/*
Logs:
{
  userId: 1,
  id: 1,
  title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
  body: 'quia et suscipit
' +
    'suscipit recusandae consequuntur expedita et cum
' +
    'reprehenderit molestiae ut ut quas totam
' +
    'nostrum rerum est autem sunt rem eveniet architecto'
}
*/

JavaScript Modules

JavaScript moduulit on tapa siirtää JavaScript koodia tiedostosta toiseen modulaarisesti.

Selaimessa toimivat modulet ovat ns. JavaScriptin natiivi moduleita. Niistä käytetään myös nimitystä ES Modules tai ESM.

JavaScriptissä on myös muita module järjestelmiä, esim. Common Js modulet, tällä opintojaksolla käytetään kuitenkin pelkästään ES Moduleja.

Kun selaimessa halutaan ottaa käyttöön JS Modulet käyttöön niin script tägiin laitetaan attribuutti type="module"

export

./index.html

html
<script type="module">
...
</script>

Kun module tyyppi on laitettu tägille niin tägien sisässä voidaan hakea import avainsanalla muista tiedostoista esim. exportattuja funktioita.

./index.js

js
export const init = (event) => {
    const app = document.querySelector('#app')
    ...
}

./index.html

html
<script type="module">
import { init } from './index.js'; 

document.addEventListener('DOMContentLoaded', init)
...
</script>

Esimerkissä käytettiin named export tyyppistä vientiä, mikä tarkoittaa että import:ssa täytyy tuoda export tismalleen samalla nimellä kuin se on exportattu. Tässä käytetään Object destructuring syntaksia.

export default

Vaihtoehtoisesti viennin voi tehdä myös default exporttina, ero nimettyyn exporttiin on se että yhdessä js tiedostossa voi olla vain yksi export default.

./config.js

js

const config = {
    secret: 'mlkd8703bnn988003fhnk79bbjkosd9080989bjhsdbj...'
    ...
}

export default config

Kun export defaultin tuo toiseen tiedostoon se täytyy nimetä itse, esim. tässä tapauksessa "GlobalConfig".

./auth.js

js
...
import GlobalConfig from './config.js'

consumeSecret(GlobalConfig.secret)
...

import *

Jos tiedostossa on useampi nimetty export niin on myös mahdollista tuoda kaikki exportit kerralla:

./tests.js

js
export const exampleOne = function() {
    ...
}

export const exampleTwo = function() {
    ...
}

./initTests.js

js
import * as Tests from './tests.js'

Tests.exampleOne()
Tests.exampleTwo()

Lapin AMK:n Web-ohjelmointirajapinnat opintojakson nettisivu.