JavaScript

JavaScript on ohjelmointikieli jota käytetään erityisesti WWW-sivujen yhteydessä.
# V2

JavaScript-perusteet

# luentomallisovellus

luentomalli

let x = 1.0;
let s = "Diiba daaba";
let foo = "1";
let totuus = true;
let apu = "foo" + "bar";
console.log( x - foo);

//tottuus = false;
let foobar;



{
    let foo = "2";
}

console.log(foo);

console.log( "undefined" == undefined );

console.log ( 1 + undefined );
let eiole;
console.log( 1+eiole);

console.log ( isNaN(1+eiole) );

console.log( NaN === NaN );

let taul = [10,20,30,40,50];
taul.oma = "foobar";


for (let i in taul) {
	console.log( i + " : " + taul[i] );
}

console.log("----");
for (let i of taul) {
	console.log( i + " : " + taul[i] );
}

let uusitaulukko = taul;

uusitaulukko[0] = 55;

console.log(taul);
console.log(uusitaulukko);

let setti = new Set(taul);

console.log(taul);
console.log(setti);

// let funkkari = function apu(x,y) { ... };

let saatilat = {
  "jämsä": "Aurinkoinen",
  äänekoski: "Pilvipoutaa"
};
saatilat.jyvaskyla = "vettä sataa";
saatilat["helsinki"] = "aurinkoa";


for (let i in saatilat) {
	console.log( i + " : " + saatilat[i] );
}

for (let paikka of Object.keys(saatilat)) {
	console.log( paikka + " : " + saatilat[paikka] );
}

 

Row 71: TypeError: saatilat isnot iterable Interaktiivisesti voi pelata vain kahta ensmmäistä console.log ja sitten error stoppaa tulosteen

17 Jan 22 (edited 17 Jan 22)

Vika on korjattu

18 Jan 22

Tuloksiin tulee edelleen ain kaksi ensimmäistä console.log() tulosta.

18 Jan 22

malli.js on edelleen muuttamatta, samalla lilla kuin vieressä on muutettu koodia.

18 Jan 22

Onko tämä siis se mistä puhuttiin tunnilla, joka kysyy avaimet? for (let paikka of Object.keys(saatilat)) { console.log( paikka + " : " + saatilat[paikka] ); }

19 Jan 22

Luennolla tehty esimerkki: malli.html ja malli.js

1. ECMAscript

JavaScript on alunperin kehitetty Netscape-selaimeen 1990-luvulla. JavaScript-kieli on nykyään standardoitu ECMAScript-standardissa. ECMAScript-kieltä kehitetään edelleen. Tällä hetkellä uusin versio on ECMAScript 2019 mutta käytännössä käytössä on ECMAScript 2015 (ES6).

Voit kirjoittaa JavaScriptia uusimmalla kielen versiolla ja kääntää Babelin avulla vanhempaan versioon.

JavaScript (ECMAScript) -kieltä käytetään useissa eri yhteyksissä, mutta erityisesti WWW-selaimissa. Selaimessa suoritetulla JavaScriptilla voi muokata selainympäristöä ja siihen liittyviä objekteja.

  • JavaScript ei liity millään tavalla Java-ohjelmointikieleen
  • JavaScriptissä käytetään dynaamista tyypitystä. Jos haluat staattisen tyypityksen niin ks. TypeScript
  • JavaScript on prototyyppipohjainen kieli eikä luokkapohjainen kuten esim. Java

vahva tyypitys = tyyppi pysyy, heikko/olematon tyypitys = tyyppi muuttuu staattinen tyypitys = tyyppi kirjoitetaan lähdekoodiin, dynaaminen tyypitys = tyyppi päätellään arvosta

Tuossakin on vähän epäselvyyksiä, mutta myöhemmät kurssit selvittävät asian. -ji

05 Feb 21

2. Visual Studio Code ja JSHint

Hyvä editori Javascriptin kirjoittamiseen on Visual Studio Code. Huom. tämä on eri asia kuin Visual Studio.

Visual Studio Coden liveserver-ominaisuudesta voi olla paljon apua.

On myös muita vaihtoehtoja. Visua Studio Code on varsin iso ja raskas ohjelma. Moniin pikkukokeiluihin riittää selaimen sisältämät kehitystyökalut.

05 Feb 21 (edited 05 Feb 21)

Visual Studio code live server

JSHint on työkalu, joka automaattisesti tarkistaa javascript-koodin laatua ja virheitä. JSHintin voi asentaa laajennoksena Visual Studio codeen.

Valitettavasti tämä ei vielä riitä vaan varsinainen JSHint pitää asentaa npm:n avulla joko omalle koneelle tai lokaalisti siihen kansioon, joka on Visual Studio Codessa avoinna. Npm on asennustyökalu, joka tulee nodejs:n mukana. Nodejs on ympäristö, jolla voi suorittaa javascriptia muuallakin kuin selaimessa. Node.js ei liity tämän kurssin aiheisiin, joten emme mene sen käyttämiseen tarkemmin. Yliopiston mikroilla ei nodea ole asennettuna, joten JSHintin asentaminen on hieman hankalaa. Jalava- ja Halava-palvelimista node ja npm löytyvät, mutta ne ovat hieman vanhat versiot.

Seuraavassa paketissa pitäisi olla valmiina kaikki JSHintin tarvitsemat kirjastot.

http://appro.mit.jyu.fi/tiea2120/jshint.zip

Purkakaa paketti Visual Studio Codessa avoinna olevan kansiorakenteen juureen. Asentakaa Visual Studio Codeen JSHint-laajennus. Jos kaikki toimii niin Problems-ikkunaan ilmestyy JSHintin ilmoituksia. Paketin mukana on myös .jshintrc-tiedosto, johon on määritelty sopivat säädöt JSHintin tarkistuksille. Niitä voi tarpeen mukaan itse muutella. JSHintin Asetuksia voi määritellä myös Visual Studio Coden settingsien kautta.

3. JavaScript-ohjelman suorittaminen

JavaScript-ohjelmat suoritetaan yleensä selainympäristössä. Tämä tarkoittaa JavaScript-sovelluksen linkittämistä WWW-sivun yhteyteen. JavaScript-ohjelma pääsee käsiksi kyseisen WWW-sivun sisältöön ja sen ulkoasuun.

JavaScript liitetään www-sivuun script-elementillä yleensä head-osassa:

<script type="text/javascript" src="jokutiedosto.js"></script>

Mallipohja:

<!DOCTYPE html>
<html lang="fi">
<head>
<meta charset="UTF-8" />
<title>Mallipohja</title>
<script type="text/javascript" src="malli.js"></script>
</head>
<body>
</body>
</html>

JavaScriptiä voi harjoitella myös suoraan selaimessa konsolin tai konsolin avulla tai JSFiddle- ja jsbin-palveluissa.

console.log-funktiolla voit tulostaa tietoja suoraan selaimen konsoliin.

console.log("testi");

How you can improve your workflow using the JavaScript console

Luennolla näytetty malli2020.html ja malli2020.js.

4. Työvälineet

5. Syntaksi

JavaScriptin syntaksista kannattaa lukea seuraavia lähteitä:

Suositeltavaa on suorittaa ohjelma Strict modessa. Kirjoita ohjelmakoodisi alkuun rivi:

"use strict";

Lisää myös seuraava rivi, jos haluat visual studio coden ajavan TypeScriptin tyyppitarkistuksen ohjelmakoodillesi. Koodisi ei tarvitse olla TypeScriptiä. Mahdolliset ongelmat näet Visual Studio Coden problems-ikkunassa View|Problems (Ctrl+Shift+M) Saatat saada myös jotain turhia virheilmoituksia.

    //@ts-check

5.1 Ympäristö

JavaScriptiä suoritetaan määrätyssä ympäristössä, joka voi olla esimerkiksi selaimessa. JavaScript ei itsessään määrittele, miten tietoa voidaan syöttää tai tulostaa, vaan se tarjoaa ainoastaan tiedon käsittelymekanismit. Tiedon syöttö ja vasteiden anto tehdään käyttäen HTML:ää ja Document Object Model (DOM) -rajapintaa.

5.2 Yleistä

  • Isoilla ja pienillä kirjaimille on merkitystä (Foo on eri kuin foo)
  • Merkistönä käytetään Unicodea
  • Lauseet päättyvät puolipisteeseen (;). Javascript ei kuitenkaan kaikissa tilanteissa kaipaa puolipistettä (;) lauseen loppuun. On erittäin suositeltavaa käyttää puolipistettä aina, paitsi lohkon jälkeen.
  • Kommentit merkitään kuten C++:ssa: // ja /* */

5.3 Muuttujat ja arvot

JavaScript on dynaamisesti tyypitetty kieli. Muuttujien tyyppi määräytyy siis arvon perusteella. Jokaisen arvon tyyppi kuitenkin on pysyvä ja selvitettävissä, joten kyse ei ole heikosti tyypitetystä tai tyypittömästä kielestä.

Voit halutessasi käyttää TypeScript-kieltä, joka lisää JavaScriptiin käännösaikaisen tyypintarkastuksen. TypeScript-koodi käännetään tavalliseksi JavaScriptiksi.

JavaScriptissä muuttujat tulee esitellä ennen käyttöä. Tämä tapahtuu avainsanalla let tai var seuraavasti:

TODO: TypeScript-kommentti on ihan väärässä paikassa.

05 Feb 21
# alku
let a = 0;
var b;

 

Yllä olevien rivien suorituksen jälkeen a:n arvo on 0 ja b:n arvo on undefined, eli määrittelemätön. Avainsanalla let esitellyt muuttujat tulee alustaa ennen käyttöä, joten ne eivät koskaan voi olla arovoltaan undefined.

Avainsana var oli yleinen ennen kuin let lisättiin kieleen. var kuitenkin sisälsi monia sudenkuoppia, joten nykyään on parempi opetella heti aluksi käyttämään let-avainsanaa. Lyhyesti näillä on erona edellä esitellyn lisäksi se, että let-avainsanalla esitellyt muuttujat ovat käytettävissä vain esittelylohkonsa sisällä, kun taas var-avainsanalla esitellyt valuvat lohkon ulkopuolelle. Lohko on karkeasti määriteltynä {}-sulkuparin välinen alue. var-avainsanalla määritellyn muuttujan näkyvyysalueen rajaa kuitenkin se funktio, jossa sitä on käytetty, joten ei var globaaleja muuttujia luo. Sillekin on vielä käyttönsä. Tarkemmin asiasta voi lukea vaikkapa Mozillan kehittäjäsivuilta tai getifyn kirjoituksesta Githubissa.

Eli käytä tästä eteenpäin avainsanaa let, ja vain jos on pakko tai käyttämäsi selain ei sitä tue, käytä var.

Monissa kielissä let esittelee kertaalleen sijoitettavan muuttujan, jonka arvoa ei voi muuttaa. Jos tarvitset tällaisen JavaScriptissä, käytä muuttujan esittelyyn avainsanaa const.

Muuttujien tyyppiä ei tarvitse esitellä, sillä kuten parametrit, ne ovat viitteitä arvo-olioihin. Korostetaan tässä vielä, että arvon tyyppi ei voi JavaScriptissä muuttua, vaikka arvo voidaan implisiittisesti muuttaa toiseksi tarvittaessa.

5.3 Tietotyypit

  • Boolean
  • null
  • undefined
  • Number
  • String
  • Symbol
  • Object

Javascript muuntaa automaattisesti muuttujan tyyppiä tilanteen mukaan.

# watman

Olion arvo arvotaan implisiittisessä muunnoksessa

// Yhdistetään 16 tyhjää alkiota laskutoimituksen "wat"+1 arvolla
let wat1 = Array(16).join("wat" + 1);
console.log(wat1);

// Yhdistetään 16 tyhjää alkiota laskutoimituksen "wat"-1 arvolla
let watman = Array(16).join("wat" - 1) + " Batman!";
console.log(watman);

 

Eikä muuta! JavaScriptissä käytetään viitteitä. Lisäysfunktio + tuottaa erilaisia olioita sen mukaan, mitä sen parametrit ovat. Siksi sitä ei kannata käyttää holtittomasti! -ji

05 Feb 21

Ensimmäisessä tapauksessa rivillä 2 on yhteenlaskun tulos merkkijono, sillä numero muutetaan ensin merkkijonoksi ja tämän jälkeen liitetään ensimmäiseen merkkijonoon.

Jos hampaita kiristää ja silmiä siristää, liitostamisen voinee tulkita yhteenlaskuksi, mutta silti... ei! Tarjolla on toki parempi metodikin:

    "wat".concat(1);

Tuo on juuri se, mitä tuossa muka-yhteenlaskussa tehdään.

Toisessa tapauksessa rivillä 6 vuorostaan on tyystin outo yritys vähentää jotain merkkijonosta. Sitä saa mitä tilaa: NaNtarkoittaa ”not a number”.

Kannattaa siis aina olla tarkkana ja pyrkiä aina välttämään implisiittistä arvon muutosta.

Muuttujat esitellään seuraavasti:

# muuttujat
var x = 1.0;
var s = "Diiba daaba";

totuus = true; // globaali muuttuja, näkyy kaikissa funktioissa. Ei toimi Strict-moodissa. Älä käytä.

// lohkon sisäinen muuttuja
let foo = "daaba dii";

// vakio, ei voida muuttaa
const foobar = "vakio";

var bar; // määrittelemätön arvo, undefined

if ( bar === undefined ) {
   console.log("bar on undefined");
}

if (bar) {
 // jos bar on tosi
} else {
  // tämä toteutuu, koska undefined on sama kuin false
}

var luku = bar + 1; // luku saa arvoksi NaN

 

Row 6: ReferenceError: assignment to undeclared variable totuus

18 Jan 22

Muuttujien nimeämissäännöt ovat kuten Javassa.

Jos muutat String-arvon Number-arvoksi niin tee se parseInt- tai parseFloat-funktiolla.

kts. myös isInteger

Huomioi seuraavaa:

  • Liukulukujen tarkkuus n.15 desimaalia.
    0.2 + 0.1111 = 0.31110000000000004
    Tulos on pyöristettävä sopivaan tarkkuuteen.
  • Muuttujalla voi olla myös muutama erikoisarvo:

5.2.1 Lohkot

Muuttujan näkyvyys rajoittuu funktion sisälle, jos muuttuja esitellään var-etuliitteellä. Ennen ECMAScript 2015:sta ja let-muuttujanesittelyä Javascriptissa EI ole ollut lohkon sisäisiä muuttujia

function foobar() {
  var p = 1; // tämä muuttuja on voimassa vain tässä funktiossa
  {
    var p = 2; // Tällä on sama näkyvyysalue kuin edellisellä
  }
  console.log(p); // tulostaa 2
  
}

# operaattorit

5.3 Operaattorit

Käytössä on normaalit aritmeettiset ja vertailuoperaattorit. Vrt. Java

  • Vaikka 0, tyhjä merkkijono, NaN, null ja undefined ovat loogisissa operaatioissa totuusarvoltaan false, kaikki muut true, kannattaa käyttää avainsana-arvoja true ja false aina kun mahdollista.
  • !! antaa muuttujan totuusarvon
  • Matemaattisia operaatioita varten on olemassa globaali objekti Math
  • === tarkistaa ovatko saman arvoiset ja samaa tyyppiä. Käytä tätä jos yrität tutkia onko jokin null tai undefined. === ja !== operaattorit eivät muunna vertailtavana olevia objekteja mitenkään. Tavallinen == yrittää muuntaa objektit samantyyppisiksi.
    1 === "1" // epätosi
    1 == "1" // tosi
    

Vertailuissa on oltava tarkkana, sillä (löyhä) yhtäsuuruusoperaattori == pyrkii tekemään liikaa implisiittisiä oletuksia. On helpompi käyttää (tiukkoja) operaattoreita === ja !== sekä olioiden vertailumetodeja noiden enemmän arvaavien (löyhien) operaattorien (== ja !=) sijaan. EcmaScript 6 tuo tarjolle myös metodin Object.is(tämä, tuo), joka vastaa tiukkaa yhtäsuuruutta muille paitsi numeroille +0, -0 vertailtaessa arvoon NaN. (Ks. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness)

delete-operaattori poistaa objektin, ominaisuuden tai elementin taulukosta.

typeof-operaattori palauttaa merkkijonon, joka kertoo kohteena olleen objektin tyypin.

in-operaattori palauttaa true jos kysytty ominaisuus löytyy kohteena olevasta objektista.

instanceof-operaattorilla voi testata objektin tyypin

new-operaattorilla voi luoda uusia objekteja

# strings

5.4 Merkkijonot

Merkkijonot ovat olioita, joilla mm. seuraavia metodeja:

Merkkijonoja voi operoida seuraavilla operaattoreilla:

+ Liittää merkkijonot yhteen < > <= >= == Vertailee aakkosjärjestystä s[n] Antaa merkkijonon s n:nnen kirjaimen

Merkkijonoja voi siis käsitellä kuin taulukoita eli merkkijono.charAt(0) on sama kuin merkkijono[0]

Merkkijonoja voidaan Javascriptissa yhdistellä yksinkertaisesti +-operaattorilla

let foo = "tämä" + "tämä"
Myös taulukosta voi helposti tehdä merkkijonon ja käyttää haluttua erotinmerkkiä
let foo = ["teksti1","teksti2", "teksti3"]
console.log( foo.join(";") );

Merkkijonojen tyyppien muunnoksiin on olemassa muutama globaali funktio:


let luku = parseInt("567");
let luku = parseInt("567", 10); // toinen argumentti kertoo kantaluvun. Tässä kymmenjärjestelmä
let luku = parseInt("0567"); // ei toimi kuten voisi kuvitella. etunolla tarkoittaa, että luku saatetaan käsitellä oktaalijärjestelmässä.
let luku = parseInt("0567", 10); // tämä toimii
let luku = parseInt("101", 2); // binääriluku
# strings1
let s = "kissa istuu puussa";
let palat = s.split(" ");
console.log(palat);

let [kuka, mita] = s.split(" ", 1);
console.log(kuka, mita);

 

5.5 Ehtolauseet

5.5.1 if...else:

if (ehto) {
   jos oli tosi...
} else {
   jos oli epätosi...
}

5.5.2 switch

switch (expression) {
  case label_1:
    statements_1
//    break;
  case label_2:
    statements_2
//    break;
//    ...
  default:
    statements_def
}

Avainsana else kuuluu samalle riville sulkujen } ja { kanssa. –Tyylipoliisi

05 Feb 21

5.5 Silmukat

for- ja while-silmukat toimivat kuten Javassa.

Labelilla voi nimetä tietyn loopin jolloin voi break- tai continue-komennolla viitata tähän looppiin.

ulompi:
    while(true) {
        console.log('ulompi silmukka');
        let x = 0;  
        while(true) {
          console.log('sisempi...');
          x = x + 1;
          if ( x > 10) {
            break ulompi;
         }
        }
   }

5.6 for..in ja for..of

for..in ja for..of toimivat hieman eri tavoilla.

# forofjaforin
let taul = [3, 5, 7];
taul.foo = 'kukkuu';

for (let i in taul) {
   console.log(i); //  "0", "1", "2", "foo"
}

for (let luku of taul) {
   console.log(luku); //  3, 5, 7
}

// PS Taulukon lokittamiseen on nätimpikin keino:
//   console.table(taul);
// Se vain aiheuttaa timissä olevassa javascriptissä toistaiseksi virheen ... :(

 

Note: for...in should not be used to iterate over an Array where the index order is important. Millainen esimerkki tähän sopisi? Eikös Array indeksi ole aina numeerinen ja järjestyksessä?

19 Jan 22

for..in käy läpi taulukon avaimet eli alkioiden paikkanumerot taulukossa ja myös itse määriteltyjen attribuuttien nimet. for..of käy läpi oikeat taulukon arvot

5.5 map, reduce, filter, every

Esim. taulukoille on tarjolla metodit map, reduce, filter, every, ...

# kontrolliJS

For-silmukka reduce():lla

let r = 0;
const arr = [1, 2, 3, 4];

for (let i = 0; i<arr.length; ++i) r += arr[i];

console.log(r);

let r2 = 0;
for (let i in arr) r2 += arr[i];

console.log(r2);

function add(x,y) { return x+y; }
let r3 = arr.reduce(add, 0);

console.log(r3);

 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

18 Jan 22
# mapreducueveryexample
let opis = [
   "Testi",
  {
    "nimi": "Maija Meikäläinen",
    "Syntymäaika": 1999,
    "Pääaine": "TIE",
    "Kurssit": ["TIEA2120","ITKP101", "ITKP1011" ]
  },
  {
    "nimi": "Matti Meikäläinen",
    "Syntymäaika": 1999,
    "Pääaine": "TIE"
  },
  {
    "nimi": "Kalle Kehveli",
    "Syntymäaika": 1998,
    "Pääaine": "TIE"
  },
  {
    "nimi": "Kaija Kehveli",
    "Syntymäaika": 1998,
    "Pääaine": "TJT"
  },
  {
    "nimi": "Ville Virtanen",
    "Syntymäaika": 1995,
    "Pääaine": "MAT"
  }
]

// map käy läpi koko opis-taulukon. CurrentValue on yksittäinen käsittelyssä oleva taulukon
// arvo, index on currentValuen indeksi taulukossa ja array on käsiteltävänä oleva taulukko
// eli tässä tapauksessa sama kuin opis
// vain ensimmäinen parametri on pakollinen, muut ovat valinnaisia
// kts. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
// map palauttaa uuden taulukon johon lisätään funktion palauttamat arvot
// jos et aio palauttaa uutta taulukkoa niin ÄLÄ KÄYTÄ MAPIA. Käytä for-of tai forEach
let nimet = opis.map(function (currentValue, index, array){
    return currentValue["nimi"]; // tämä lisätään mapin palauttamaan taulukkoon
//    console.log(array);
})
log(nimet);

// forEach käy myös kaikki taulukon alkiot läpi, mutta ei palauta mitään
opis.forEach(function (currentValue, index){
    console.log(index)
    log(currentValue);
//    console.log(array);
})

// filter ottaa samat parametrit kuin map
// filter suodattaa tuloksena palautettavan taulukon eli funktio palauttaa true
// tai false. Ne elementit joiden kohdalla palautusarvo on true lisätään palautettavaan
// taulukkoon
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
let nimet2 = opis.filter(function (currentValue, index, array){
    // jos nimi alkaa K-kirjaimella niin kelpaa
    if ( currentValue.nimi && currentValue.nimi[0] == "K") return true;

    return false;
})
log(nimet2);

// every testaa päteekö jokin ehto kaikkiin taulukon arvoihin
let tulos = opis.every(function (currentValue){
    // onko kaikilla nimi?
    // testillä ei ole eli tämä palauttaa sen kohdalta false jolloin koko funktion tulos on false
    if ( currentValue.nimi ) return true;
    return false;
})
log(tulos);

let initialValue = 0;
// reduce suorittaa funktion kaikille taulukon arvoille siten, että lopputulos on yksi arvo
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
let summa = opis.reduce(function (accumulator, currentValue, index, array){
    if ( currentValue["Syntymäaika"] ) {
        return accumulator + currentValue["Syntymäaika"];
    }
    return accumulator;
}, initialValue)

console.log(summa);

 

5.6 Poikkeukset

try {
   kokeillaan jotain ...
   // throw 'no ny sekos';
}
catch (e) {
  tuli poikkeus...
  console.log(e);
}

Error types

5.7 Promise

Käytetään asynkronisten funktioiden yhteydessä. Tähän palataan TIES4080-kurssilla.

Using promises

5.8 Taulukot

Taulukko luodaan Array-oliolla

let paikat = new Array();
paikat[0] = "Jämsä";
paikat[1] = "Äänekoski";

tai hakasulkeilla:

let paikat = ["Jämsä", "Äänekoski"];
  • Taulukon indeksinä on aina numero.
  • Taulukon viimeistä seuraavan indeksin saa taulukko.length -metodilla.
  • Huom.
    paikat[100] = "Helsinki";
    length on nyt 101 eikä 3!

Taulukkoon lisääminen onnistuu seuraavasti:

paikat[paikat.length] = "Jyväskylä";
paikat.push("Jyväskylä");

Taulukko on kuten Object, mutta taulukolle on määritelty length

Taulukot ovat oikeasti osoittimia. Taulukkoa ei voi kopioida pelkällä sijoitusoperaatiolla. kts. Array.from ja slice, jotka tekevät shallow-kopion taulukosta. Jos haluaa oikean kopion, niin kopiointi on ohjelmoitava itse.

# array1
let taul = [3, 5, 7];
console.log(taul.length);
taul[6] = 99;
console.log(taul, taul.length);
console.log(taul[10]);
console.log(taul.includes(3));
console.log(taul.includes(4));

 

5.8 Delete vs. splice

JavaScript-taulukosta poistaminen tapahtuu splice-metodilla.

On olemassa myös delete-operaattori, joka poistaa ominaisuuksia objekteilta. Taulukon alkioiden yhteydessä tämä tarkoittaa käytännössä taulukon alkion vaihtamista undefined-tyyppiseksi. Taulukon koko tai indeksointi ei muutu, jos käyttää delete-operaattoria. Delete on tarkoitettu objektien ominaisuuksien poistamiseen eikä taulukon alkioiden poistamiseen.

Taulukon alkiot voi helposti yhdistää join()-metodilla.

5.8 Set

Set on taulukko johon voi tallentaa vain uniikkeja arvoja.

5.8 Map

Map on tietorakenne johon voidaan tallentaa avain-arvo-pareja. Vrt. Python dict. Myös Javascriptin omat objektit toimivat vastaavanlaisena tietorakenteena.

6. Funktiot

JavaScriptin funktiot ovat oikeastaan proseduureja, sillä ne voivat palauttaa tai olla palauttamatta paluuarvoa. Lisäksi funktiot voivat aiheuttaa sivuvaikutuksia. Funktion esittelyssä määritellään funktion nimi, sekä funktion parametrien nimet. Parametrien tyyppejä ei tarvitse kertoa, sillä (käytännössä) kaikki arvot JavaScriptissä ovat olioita. Parametrit ovat siten viitteitä olioihin.

Javascriptin funktiokutsuissa on oletuksena käytössä pass by value, paitsi jos parametreina on objekteja tai taulukoita jolloin toimii pass by reference.

Funktion voi esitellä seuraavasti:

# Plugin1
function summaa(x,y) {
  return (x + y);
}

summaa = function(x,y) {
  return (x + y);
}

function foobar(x,y) {
  // tämä funktio toimii vain funktion foobar-sisällä
  var p = 1;
  function barfoo(x) {
    // ulomman funktion muuttujat ovat käytettävissä
    return x*p;
  }

}

// parametreille voidaan antaa <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters">oletusarvot</a>
function f(x=0,y=1) {
  return x+y;
}

 

Huomioitavaa:

  • Funktiota voi kutsua ylimääräisillä parametreilla. Kaikki funktion saamat parametrit voi käydä läpi arguments-taulukolla.
  • Jos funktio ei palauta return-lauseella arvoa on funktion palautusarvo undefined
  • Arrow functions

JavaScriptissä ei ole funktion kuormitusta, vaan parametrien arvon olemassaolon joutuu itse testaamaan:

# jsFunctionOverload
function lisaa(a, maara) {
    if ( maara !== undefined ) return a+maara;
    return a + 1;
}

console.log(lisaa(2));
console.log(lisaa(2,0));
console.log(lisaa(3,4));

 

  • edellä voitaisiin käyttää myös muotoa:

    return a + (maara || 1);  // HUOM! Toimii tässä väärin!

    mutta jos maara on 0, niin silloinkin m saa arvon yksi. Eli se ei sovi tähän esimerkkiin.

7. Objektit

Assosiatiivisia taulukoita (nimi-arvo-pareja) voi tehdä seuraavasti (vrt. Map):

Tämä alku on kyllä harhaanjohtava. Assosiatiiviset taulukot (vrt. pythonin dict) tehdään nykyään Map-oliolla. Oliot olioina ja mapit mappeina. (Korjataan kun ehditään.) -ji

05 Feb 21

Jonne, ei pidä paikkaansa. Assosiatiiviset taulukot voidaan tehdä Map-oliolla, mutta käytännössä näin ei kuitenkaan tehdä. Esim. JSON-muotoista dataa käsiteltäessä ei Mapia käytetä vaan aina kyseessä ovat objektit.

05 Feb 21

Jep, JSONille hyväksyisin tuon, mutta muuten kannattaisikäyttää Map’ia. Ks. Map vs. Object MDN:stä. -ji

05 Feb 21

Toki kannattaisi käyttää Mapia, mutta käytäntö on vaan ihan eri.

05 Feb 21

Käytäntö on mitä on, mutta sekin voi parantua. On typerää pysyä vanhoissa huonoissa tavoissa vain tavan vuoksi.

05 Feb 21

Niin kauan kuin JSON-muotoista dataa liikutellaan on pakko käyttää objekteja. Javascriptin oma JSON.stringify ei osaa muuntaa Map-olioita JSON-muotoon.

17 Jan 22
# objektit11
// Vanha tapa:
let lampo = new Object;
lampo["jkl"] = -6;
let saatilat = {
  jämsä: "Aurinkoinen",
  äänekoski: "Pilvipoutaa"
};

for (let i in saatilat) {
	console.log( i + " : " + saatilat[i] );
}

// Nykyinen tapa:
let lämpö = new Map();
lämpö.set("jkl", -6);

let säätilat = { // Tämä käytössä JSONina
  jämsä: "Aurinkoinen",
  äänekoski: "Pilvipouta"
}

for (let i in säätilat) {
	// console.log("%i : %o", i, säätilat[i] ); // nykyjavascript selaimessa
	console.log( i + " : " + säätilat[i] ); // paikallinen javascript
}

// Tai vielä helpommin:

// console.table(säätilat);  // kunhan timin javascriptkin tuntee tämän

 

Objektin sijoitus muuttujaan tarkoittaa objektiin viittaavan viitteen sijoitusta muuttujaan. Objektia ei voi siten kopioida pelkällä sijoitusoperaatiolla. Katso Object.assign(), jos haluat tietää, kuinka objektista tehdään matala (shalllow) kopio.

Yllä olevan esimerkin ssosiatiivinen taulukko säätilat on itseasiassa objekti, jolla on attribuutteja. Tämä voi aiheuttaa jossain tilanteissa hämmennystä. Vrt. Map

JavaScript (joka pohjautuu ECMAScript 3 -standardiin) on prototyyppi-pohjainen kieli ja siinä ei ole luokkia samassa mielessä kuin Java/C++/C#-kielissä vaan pelkästään olioita (objekteja).

  • JavaScriptissä on vain olioita, ei luokkia. Jos huomaat ajattelevasi luokkia, lepää hetki, poista luokat mielestäsi, ja jatka eteenpäin.
  • Uusimmassa JavaScriptissä (EcmaScript 6) on class-avainsanalla merkitty rakenne olioiden ja prototyyppien esittelyyn, vaan (onneksi) ei vieläkään luokkia.
  • Jokaisen olion piirteet ovat joko sen omia, tai sen prototyyppiolion omia. Lisäksi ne ovat dynaamisia, eli niitä voi lisätä ja poistaa ajon aikana.
  • JavaScriptissä funktiotkin ovat olioita.
  • ... ja funktiot ovat paremminkin proseduureja, sillä ne voivat olla palauttamatta paluuarvoa ja aiheuttaa sivuvaikutuksia.

JavaScriptissä omia olioita voidaan luoda seuraavasti:

# objektit22
// konstruktori-funktio
function Koordinaatti(x,y) {
  this.x = x;
  this.y = y;
}

// Koordinaatin "public-metodi"
Koordinaatti.prototype.normi = function() {
  return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
}

let piste = new Koordinaatti(1,2);
piste.normi();

// Voidaan käyttää myös modernimpaa class-syntaksia:
class Polygon {
  constructor(height, width) {
    this.name = 'Polygon';      // "Katso, äiti: "
    this.height = height;       // "Attribuutit esitellään"
    this.width = width;         // "ihan kuin Pythonissa!"
  }
}
class Square extends Polygon {
  constructor(length) {
    // super kutsuu Polygonin konstruktoria. Suoritettava ennen kuin voi käyttää this
    super(length, length);
    this.name = 'Square';
  }
}
let poly = new Polygon(10, 10);
let nelio = new Square(20);

 

Objekteja laajennetaan toisten objektien avulla käyttäen prototype-määritystä. Private-attribuutteja ja -metodeja on mahdollista luoda pienellä vaivalla.

// muistakaa, että attribuutteja (avaimia) voi vapaasti keksiä
let testi = new Object();
undefined
testi.foo;
undefined
testi.foo==undefined
true
testi.foo = "koe";
"koe"
testi.foo==undefined;
false
delete testi.foo;
true
testi.foo==undefined;
true
// muistakaa, että objekteilla testi.bar on sama asia kuin testi["bar"]
testi["bar"] = "koe2";
"koe2"
testi.bar;
"koe2"
testi.bar==undefined;
false
delete testi["bar"];
true
testi.bar==undefined;
true

7.1 Olion alustus vanhalla tavalle

HUOM! Katso uudempaa käyttöä seuraavan class-luvun nykyisin suositeltava tapa. Tämä vanha tapa esitetään tässä, koska (vanhassa) koodissa tätä vielä näkee paljon ja se on syytä tarvittaessa tunnistaa.

Olion luomista varten on tehtävä olion rakenninfunktio, jonka nimeä käytetään puhuttaessa yleisesti funktiolla luoduista olioista. Vertaa, miten Javassa tai C#:ssa alustajametodin nimi on sama kuin luokan nimi.

Jos tätä rakenninfunktiota kutsutaan muodossa new FunktionNimi(param) on funktion määrittelyssä käytettävissä this-viite olioon, jonka arvon new palauttaa.

# olioJS

Olion rakennin ja prototyyppi

function Laskuri(kohde, arvo) {  // Rakentaja
    if (kohde !== undefined) this.kohde = kohde;
    else this.kohde = '???';
    this.arvo = arvo || 0;
}

Laskuri.prototype.lisaa = function (maara) {// Metodi
    let m = 1;
    if (maara !== undefined) m = maara;
    this.arvo += m;
};

Laskuri.prototype.toString = function () { // toString uudelleen
    return this.kohde+': '+this.arvo;
};

let lask = new Laskuri('auto');     // Luodaan Laskuri-olio yhdellä argumentilla
lask.lisaa(); lask.lisaa();         // pari metodikutsua
console.log(lask);                  // kutsuu automaattisesti lask.toString()


function HienoLaskuri() {           // Rakentaja
    Laskuri.apply(this,arguments);  // Viedään samat parametrit Laskurille
}                   // arguments-viite on käytössä funktio-olion sisällä ja
                    // se ei ole taulukko, vaikka käyttäytyykin niin

HienoLaskuri.prototype = new Laskuri();  // Luodaan Laskurin proto pohjaksi
HienoLaskuri.prototype.constructor = HienoLaskuri; // Rakentaja takaisin oikeaksi

HienoLaskuri.prototype.toString = function () { // erilainen toString
    return 'Elegantisti: '+ Laskuri.prototype.toString.call(this);
};  // yllä call kuten apply aiemmin, call:ille vain annetaan parametrit
    // erikseen, ei listana, kuten applylle

let hl = new HienoLaskuri('ufo',3);   // luodaan uusi olio
hl.lisaa(); hl.lisaa(4);
console.log(hl);

 

Tämä tarviisi rinnalleen uudemman tavan version

05 Feb 21

Käviskös tuo 7.2?

05 Feb 21 (edited 05 Feb 21)

Tämän jälkeen voidaan lisätä metodit lisaa ja toStringolion prototyyppiin. Jos metodit lisättäisiin kuten attribuutit, ne monistuisivat jokaiselle oliolle. Prototyyppiolio, jonka new-operaattori kopioi, löytyy aina rakentajafunktion attribuutista prototype.

Seuraavalla kolmella rivillä testataan laskuria. Ensin luodaan laskuriolio, sen arvoa kasvatetaan kahdesti, ja sitten olio tulostetaan konsoliin.

Sitten luodaan HienoLaskuri, jotta opitaan toisen olion ”perintä”, tai tarkemmin sanottuna metodikutsun delegointi prototyypin prototyypille. Ensin määritellään rakenninfunktio HienoLaskuri(kohde, arvo).

Seuraavilla riveillä sidotaan HienoLaskuri Laskuriin, eli tässä se varsinainen ”perintä” vasta tapahtuu. HienoLaskuri-olion prototyypiksi luodaan tavallinen Laskuri-olio, joka kopioidaan aina new-operaattorilla, ja alustetaan kuntoon HienoLaskuri-rakentimella. Koska uuden Laskuri-olion sijoitus prototyypiksi muuttaa HienoLaskuri.constructor-attribuutin osoittamaan Laskuri-rakentimeen, tulee se korjata osoittamaan takaisin rakentimeen HienoLaskuri.

Ja lopuksi taas kokeillaan, miten HienoLaskuri toimii.

7.2 class

EcmaScriptin uusin versio tuo mukanaan class-avainsanan, jota voi käyttää olion rakentimen ja prototyypin metodien esittelyyn. Tämä selkiyttää huomattavasti koodia. On kuitenkin syytä muistaa, että tämä on vain syntaktista sokeria, sillä ns. luokka on prototyyppiolio kuten ennenkin, ainakin toistaiseksi.

# olioclassJS

Olion rakennin ja prototyyppi class-rakenteella

class Laskuri {
    constructor (kohde, arvo) {  // Rakentaja
      if (kohde !== undefined) this.kohde = kohde;
      else this.kohde = '???';
      this.arvo = arvo || 0;
    }

    lisaa (maara) { // metodi
      let m = 1;
      if (maara !== undefined) m = maara;
      this.arvo += m;
    }

    toString() { // toString uudelleen
      return this.kohde+': '+this.arvo;
    }
}

let lask = new Laskuri('auto');     // Luodaan Laskuri-olio yhdellä argumentilla
lask.lisaa(); lask.lisaa();         // pari metodikutsua
console.log(lask);                  // kutsuu automaattisesti lask.toString()


class HienoLaskuri extends Laskuri {
    constructor (kohde, arvo) {
        super(kohde, arvo);
    }
    toString() { // erilainen toString
        return 'Elegantisti: '+ super.toString(this);
    }
}

let hl = new HienoLaskuri('ufo',3);   // luodaan uusi olio
hl.lisaa(); hl.lisaa(4);
console.log(hl);

                                      // Huomaa kuitenkin, että
console.log(typeof Laskuri)           // tulostaa: function
                                      // sillä Laskuri on rakenninfunktio
                                      // oliolle, kuten ennenkin.

 

7.3 Jos todella haluat ymmärtää

Jos todella haluat ymmärtää, mistä JavaScriptin prototyyppioliomallissa on kyse, suosittelen lukemaan Douglas Crockfordin kirjoituksen aiheesta Classical inheritance in JavaScript.

7.4 Funktiokin on olio

JavaScriptissä funktiotkin ovat olioita. Tämä tarkoittaa sitä, että funktioilla voi olla attribuutteja ja metodeita. Ne tekee funktioiksi se, että ne osaavat vastata funktion sovittamisviestiin (). Alla on esimerkki funktiosta oliona.

# oliofunktioJS

Funktio oliona

function foo(e) {                     // esim. miten funktiota voi kutsua
    console.log(foo.maara,e);         // olettaa funktio olion foo sisältävän
}                                     // maara-attribuutin

foo.maara = 2;                        // luodaan funktio-oliolle foo
                                      // attribuutti foo.maara

foo(23);                              // alla oleva on vastaava kutsu kuin tämä
foo.call(null, 24);                   // metodeilla olisi null:in tilalla olio

 

8. Tarpeellisia funktioita

8.1 Array.sort()

Javascriptin taulukoita järjestettäessä täytyy itse kirjoittaa oma vertailufunktio (compareFunction), jos taulukoissa on esim. objekteja tai muuta monimutkaisempaa sisältöä. Jos on huolimaton tämän funktion kanssa niin ampuu helposti itseään jalkaan. Väärin kirjoitettu funktio voi tuurilla toimia firefoxissa, mutta ei toimikaan esim. Chromessa.

Jos vertaillaan numeroita niin vertailufunktion voi kirjoittaa lyhyesti näin:

function compareNumbers(a, b) {
   return a - b;
}

Jos a ja b ovatkin stringeja niin ylläoleva funktio ei toimikkaan oikein.

Jos vertaillaan lähes mitä tahansa muuta kuin numeroita niin funktion ON oltava seuraavaa muotoa:

function compare(a, b) {
   if (a is less than b by some ordering criterion) {
     return -1;
   }
   if (a is greater than b by the ordering criterion) {
     return 1;
   }
   // a must be equal to b
   return 0;
}

Suositeltavaa on käyttää aina tätä jälkimmäistä niin ei vahingossa satu omaan jalkaan.

# sortexample
let opis = [
   "Testi",
  {
    "nimi": "Maija Meikäläinen",
    "Syntymäaika": 1999,
    "Pääaine": "TIE",
    "Kurssit": ["TIEA2120","ITKP101", "ITKP1011" ]
  },
  {
    "nimi": "Matti Meikäläinen",
    "Syntymäaika": 1999,
    "Pääaine": "TIE"
  },
  {
    "nimi": "Kalle Kehveli",
    "Syntymäaika": 1998,
    "Pääaine": "TIE"
  },
  {
    "nimi": "Kaija Kehveli",
    "Syntymäaika": 1998,
    "Pääaine": "TJT"
  },
  {
    "nimi": "Ville Virtanen",
    "Syntymäaika": 1995,
    "Pääaine": "MAT"
  }
];

log(opis);

function compare_opis(a, b) {
// undefined pitää myös huomioida. Testillä ei ole nimeä
// halutaanko undefined viimeiseksi vai ensimmäiseksi? seuraavalla lisäyksellä tulee viimeiseksi
if (!a.nimi) {
        return 1;
}

// yleensä riittää vain seuraava
if (a.nimi < b.nimi) {
// myös tämä käy:        if (a["nimi"] < b["nimi"]) {
            return -1;
    }
    if (a.nimi > b.nimi) {
        return 1;
    }
    // a must be equal to b
    return 0;
  }


opis.sort( compare_opis );
log(opis);

// vanhin ensimmäiseksi eli pienin syntymävuosi
function compare_opis_ika(a, b) {

    // yleensä riittää vain seuraava
    if (a["Syntymäaika"] < b["Syntymäaika"]) {
                return -1;
        }
    if (a["Syntymäaika"] > b["Syntymäaika"]) {
            return 1;
        }

    // samanikäiset järjestetään toissijaisesti nimen mukaan
    if (a.nimi < b.nimi) {
                    return -1;
     }
     if (a.nimi > b.nimi) {
                return 1;
     }
            // a must be equal to b
     return 0;


      }

opis.sort( compare_opis_ika );

log(opis);

 

8.1 Date

8.1.1 Päivämäärä

let aika = new Date();
// Huono tapa
console.log(aika.getDate() + "." + (1+aika.getMonth()) + "." + (1900+aika.getYear()));
// Parempi tapa
console.log(new Intl.DateTimeFormat('fi-FI').format(aika));

8.1.2 Kulunut aika

let aika = new Date();
let hetkiA_msec = aika.getTime();
// tehdään jotain
let hetkiB_msec = aika.getTime();
let kulunutaika = new Date(hetkiB_msec - hetkiA_msec);
alert(kulunutaika.getSeconds());

8.1 Math

8.1.1 Satunnaisluvut

# shell

Tässä uusi arvonta vaatii tulosikkunan sulkemisen

let sanoja = ["Diiba","Daaba","Tesmaus","Heppa","Muuli","Saippuakauppias","Jepjep"];
// pyöristys alaspäin( 0-0.999999999 * 7 )
let rand = Math.floor( Math.random() * sanoja.length );
console.log(sanoja[rand]);

 

8.1 Pyöristäminen ja numeroiden muotoilu

let liukuluku = 3.245200004;
let kaksidesimaalia = Math.round(liukuluku*100)/100

Numeroiden mutoilu on syytä tehdä Intl.NumberFormat-avulla.

# i18n-numbers
let number=123.456;
console.log(new Intl.NumberFormat('en-EN', { maximumSignificantDigits: 5 }).format(number));
// tulostuu: 123.46
console.log(new Intl.NumberFormat('fi-FI', { maximumSignificantDigits: 5 }).format(number));
// tulostuu: 123,46
console.log(new Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec', { maximumSignificantDigits: 5 }).format(number));
// tulostuu: 一二三.四六

 

8.1 isNaN

Tutkii onko muuttuja erikoisarvoa NaN (not a number). NaN-arvoa ei voi vertailla muulla kuin tällä funktiolla.

let numero = parseInt(document.getElementById("tekstikentta").value);
if (isNaN(numero))
  document.getElementById("tekstikentta").className = "red";

8.2 typeof

Operaattorit: typeof - Core JavaScript 1.5 Reference

Palauttaa merkkijonona olion tyypin. Älä käytä liikaa!

9. JSON

JSON ja javascript-tietorakenteita

Javascriptin tietorakenteet ovat lähes sama asia kuin JSON-tiedostomuoto.

JSON on standardoitu tiedostomuoto tiedonvälitykseen. JSON on suoraan javascript-yhteensopiva mutta sitä käytetään usein monen muun kielen yhteydessä. WWW-sovelluksissa JSON on erittäin yleinen tiedonsiirtomuoto.

Voit käyttää JSONina suoraan javascriptin taulukoiden ja objektien esitysmuotoja eli [] ja {} sisältämät osat:

// javascriptia
let a = [1,2,3,4];
// JSONia
[1,2,3,4]
# json2
let a = [1,2,3,4];
let b = {mene: true, vaate: 1, kengat: 2};

console.log(a);
console.log(b);
console.log(JSON.stringify(b));
console.log("Kengät: " + b.kengat);

b.luvut = a;
console.log(JSON.stringify(b));

 

Kokeile tutkia seuraavia tietorakenteita JSON-editorilla: data ja tupa.

Vastaavia rakenteita voi tutkia myös suoraan selaimen konsolissa. Avaa pohja.html-tiedosto ja konsoli.

# json1
let a = [11, 2, 3, 4];
let s = '{"x": 20, "y": 30}';

let sj = JSON.parse(s);

console.log(sj.x, sj.y, JSON.stringify(a));

 

10. Tietorakenteiden käsittelyä

Javascript-tietorakenteita

JSON ja javascript-tietorakenteita

# rakenneesim
let opis = [
   "Testi",
  {
    "nimi": "Maija Meikäläinen",
    "Syntymäaika": 1999,
    "Pääaine": "TIE",
    "Kurssit": ["TIEA2120","ITKP101", "ITKP1011" ]
  },
  {
    "nimi": "Matti Meikäläinen",
    "Syntymäaika": 1999,
    "Pääaine": "TIE"
  },
  {
    "nimi": "Kalle Kehveli",
    "Syntymäaika": 1998,
    "Pääaine": "TIE"
  },
  {
    "nimi": "Kaija Kehveli",
    "Syntymäaika": 1998,
    "Pääaine": "TJT"
  },
  {
    "nimi": "Ville Virtanen",
    "Syntymäaika": 1995,
    "Pääaine": "MAT"
  }
]

console.log(" Opiskelija on objekti ");
for (let i in opis) {
  console.log(i + " : " + opis[i]);
}

console.log(" Opiskelijan nimi attribuuttina ");
for (let i in opis) {
  console.log(i + " : " + opis[i].nimi);
}

console.log(" Opiskelijan nimi []-operaattorilla ");
for (let i in opis) {
  console.log(i + " : " + opis[i]["nimi"]);
}

console.log(" Opiskelijan nimi []-operaattorilla ja käytetty avaimena muuttujaa ");
let kentta = "Pääaine";
for (let i in opis) {
  console.log(i + " : " + opis[i][kentta]);
}


console.log(" Kaikki tiedot");
for (let i in opis) {
  if ( typeof(opis[i]) === "object" ) {
      for(let key in opis[i]) {
        console.log(key + " : " + opis[i][key]);
      }
  }
  if ( typeof(opis[i]) === "string" ) {
        console.log("String : " + opis[i]);
  }
}

console.log("Ensimmäisen opiskelijan tietoja");
console.log(opis[1]['nimi']);
console.log(opis[1]['Syntymäaika']);
console.log(opis[1]['Pääaine']);

// opiskelijan kursseja
console.log("Kurssilistaus hardkoodattuna");
console.log(opis[1]['Kurssit'][0]);
console.log(opis[1]['Kurssit'][1]);
console.log(opis[1]['Kurssit'][2]);

console.log("Kurssilistaus silmukalla of-operaattorilla");

// tulostaa ensimmäisen opiskelijan kurssit. Huomaa of eikä in eli saadaan suoraan taulukon alkio
for (let kurssi of opis[1]['Kurssit']) {
  console.log(kurssi);
}
console.log("Kurssilistaus silmukalla in-operaattorilla");
// sama kuin edellä mutta käytetään in eli saadaan indeksi
for (let i in opis[1]['Kurssit']) {
  console.log(opis[1]['Kurssit'][i]);
}

 

Kätevämpää kuin tuo indeksikikkailu olisi tehdä opiskelijasta kunnollinen olio. Jätetään se harjoitustehtäväksi.

# ifpois

11. If-lauseiden välttäminen monimutkaisissa ehdoissa

Joskus voi olla asioita jotka riippuvat hyvin monimutkaisella tavalla ympäröivistä ehdoista. Tällöin voi käyttää päätöstauluja. Oletetaan esimerkiksi, että on ehtoja:

  • sataa
  • paljon
  • lämmin

ja halutaan noiden ehtojen perustella saada tieto kannattaako lähteä pyöräilemään. Ensin kirjoitetaan totuustaulu kaikille vaihtoehdoille ja niiden perusteella että lähdetäänkö pyöräilemään:

n sataa paljon lämmin pyöräilemään muuta
0 F F F T
1 F F T T
2 F T F T ei mahdollinen
3 F T T T ei mahdollinen
4 T F F F
5 T F T T
6 T T F F
7 T T T F

Vaikka tämän totuustaulun mukaan olisikin vielä tehtävissä if-lauseilla vastaava ehto pyöräilyyn lähtemiselle, niin jos ehtojen määrä kasvaa tuosta kolmesta, niin if-lauseesta tulee vaikeasti ymmärrettävä ja ylläpidettävä. ei mahdollinen-tilanteissa ei ole väliä mitä totuustauluun kirjoitetaan, koska sellainen tilanne ei voi tulla.

Taulukon alkuun on kirjoitettu n, joka voidaan ajatella luvuksi, mikä muodostuu jos ehtojen joukko ajatellaan binääriluvuksi. Edellä esimerkiksi:

T F T  = 1 0 1 = 5

Kirjoitetaan nyt sama helppotajuisesti ylläpidettäväksi JavaScript koodiksi:

# totuus1
function pyorailemaan(sataa, paljon, lammin) {
   const ehdot = [
               // | n | sataa | paljon| lämmin  |pyöräilemään| muuta          |
               // |:-:|:-----:|:-----:|:-------:|:----------:|----------------|
       true,   // | 0 |  F    |   F   |    F    |    T       |                |
       true,   // | 1 |  F    |   F   |    T    |    T       |                |
       true,   // | 2 |  F    |   T   |    F    |    T       | ei mahdollinen |
       true,   // | 3 |  F    |   T   |    T    |    T       | ei mahdollinen |
       false,  // | 4 |  T    |   F   |    F    |    F       |                |
       true,   // | 5 |  T    |   F   |    T    |    T       |                |
       false,  // | 6 |  T    |   T   |    F    |    F       |                |
       false,  // | 7 |  T    |   T   |    T    |    F       |                |
   ];

   const avain = 4*sataa + 2*paljon + 1*lammin;
   return ehdot[avain];

}


console.log(pyorailemaan(true, false, true));

 

Samaa kikkaa voi sitten soveltaa, jos esimerkiksi tuossa pitäisi tuloksen lisäksi palauttaa tieto siitä puetaanko paljon (2) vai vähän (1) ja tuleeko sadekengät (2). Silloin boolean paluuarvon sijaan palautettaisiin vaikkapa olio (tai tarvittaessa taulukko), jossa on vaatetus:

# totuus2
function pyorailemaan(sataa, paljon, lammin) {
   const ehdot = [
                                             // | n | sataa | paljon| lämmin  |pyöräilemään| muuta          |
                                             // |:-:|:-----:|:-----:|:-------:|:----------:|----------------|
       {mene: true,  vaate: 2, kengat: 2},   // | 0 |  F    |   F   |    F    |    T       |                |
       {mene: true,  vaate: 1, kengat: 1},   // | 1 |  F    |   F   |    T    |    T       |                |
       {mene: true,  vaate: 2, kengat: 2},   // | 2 |  F    |   T   |    F    |    T       | ei mahdollinen |
       {mene: true,  vaate: 2, kengat: 2},   // | 3 |  F    |   T   |    T    |    T       | ei mahdollinen |
       {mene: false, vaate: 2, kengat: 2},   // | 4 |  T    |   F   |    F    |    F       |                |
       {mene: true,  vaate: 2, kengat: 2},   // | 5 |  T    |   F   |    T    |    T       |                |
       {mene: false, vaate: 2, kengat: 2},   // | 6 |  T    |   T   |    F    |    F       |                |
       {mene: false, vaate: 2, kengat: 2},   // | 7 |  T    |   T   |    T    |    F       |                |
   ];

   const avain = 4*sataa + 2*paljon + 1*lammin;
   return ehdot[avain];

}


console.log(JSON.stringify(pyorailemaan(true, false, true)));

 

Kuten edellä, ei oikeastaan ole väliä mikä vaatetus palautetaan jos ei olla lähtemässä pyöräilemään. Toki tuota voisi soveltaa niin, että pyöräilyn "vastakohtana" on kävely ja palautetaan sitten sitä vastaava vaatetus. Kokeile muuttaa edellistä siten.

Oikeasti vaatetus ja kengät tietona kannattaisi palauttaa jotakin kuvaavampaa kuin kokonaisluku. Tässä on vaan koitettu pitää esimerkki yksinkertaisena. Kokeile muuttaa edellistä siten, että vaatetus palautetaan merkkijonona, samoin kengät.

Seuraava askel tästä on laittaa taulukkoon lambda-funktioita.

12. Materiaalia

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.