Krister Eklund
Javascript on monipuolinen kieli, jolla saa helposti aikaiseksi monimutkaista koodia. Senpä takia on hyvä miettiä miten asioita sillä koodaa. Alla on joitain esimerkkejä omasta mielestäni selkeistä ohjelmointitavoista. Toivottavasti näistä on jotain hyötyä. Hyviä lukuhetkiä!
Javascriptin muuttujilla, parametereilla ja funktioilla ei ole tyyppiä. Joskus voisi kuitenkin olla hyödyllistä pystyä tarkistamaan onko jokin muuttujaan sijoitettu arvo haluttua tyyppiä. Tätä varten alle on koottu muutama tarkistusfunktio, jolla tyypin tarkistaminen onnistuu.
HUOM. Mikäli olet tarkistamissa käyttäjän antamaa syötettä esim. HTML-sivullasi, muista seuraava: koska Javascript-koodi suoritetaan tässä tapauksessa käyttäjän koneella, ja on täten käyttäjän muokattavissa, ei ikinä voida luottaa annettuun syötteeseen pelkillä Javascript-tarkistuksilla vaan myös palvelinpuolella on tehtävä tarkistus!
Mikäli on tarpeen selvittää onko annettu arvo kokonaisluku voidaan käyttää seuraavaa apufunktiota (lähde):
function isInt(value){ return ((typeof value === "number") && (Math.floor(value) === value)); }
Alla tuloksia:
Funktion nimeäminen muotoon isSomething ja sen palauttama totuusarvo on mielestäni selkein tapa tyypin tarkastusfunktiolle. Mikäli Infinityä ei haluta laskea numeroksi, voisi sen tapauksessa palauttaa false erillisellä if-lauseella.
Yleisesti lukujen tapauksessa toimii hyvin Javascriptin (typeOf === number) tarkistus.
function isNumber(value){ return ((typeof value ==="number") && (!isNaN(value))) }
Yllä on lisäksi haluttu, että NaN ei ole luku.
Merkkijonon selvitys onnistuu seuraavalla funktiolla: (lähde)
function isString(value){ if (value.substring) return true; else return false; }
Taulukon läpikäynti esimerkiksi kutsumalla isInt-funktiota kaikille taulukon alkioille:
function isIntArray(value){ for (i=0; i < value.length; i++){ if (!isInt(value[i])) return false } return true; }
Tällä tavalla voidaan siis tehdä vielä apufunktio, joka tarkastaa, että jokainen taulukon arvo on oikeaa tyyppiä. Numeroille tai merkkijonoille tehtäisiin funktio ihan samaan tyyliin vaihtamalla vain isInt-funktion tilalle jokin muista is-funktioista.
Se, että Javascriptin muuttujilla, parametereilla ja funktioilla ei ole tyyppiä tekee Javascriptistä voimakkaan kielen. Vaikka ylempänä teimme tyyppien tarkistusfunktioita, ei se tarkoita, että niitä pitäisi aina käyttää. Kielen ominaisuuksia vastaan ei kannata taistella vaan niitä kannattaa hyödyntää. Jos on mahdollista tehdä yksi funktio, joka osaa tehdä saman asian luvuille ja merkkijonoille, niin onhan se varsin kätevää. Koodia ei pitäisi koskaan joutua kopioimaan siten, että kahdessa eri funktiossa on suurin osa samaa sisältöä.
Tyyppien tarkastaminen voi kuitenkin välillä olla hyödyllistä, mikäli odotetaan vain tietyntyyppistä parametria. Esimerkiksi alla on funktio, joka osaa järjestää mitä vain tyyppiä sisältävän taulukon, mutta samalla tarkastetaan kuitenkin, että syöte on taulukko. Tällaisen omaSort-funktion tekeminen ei ole järkevää, mutta toivottavasti havainnollistaa asiaa. Tässä käytetään Javascriptin omaa sort-metodia. Se osaa järjestää mitä tyyppiä tahansa sisältävän taulukon parametrilla saadun järjestysfunktion avulla.
JS BinYllä olevassa esimerkissä käytetään samaa funktiota eri tyyppisille taulukoille. Toteuttaessani funktiota huomasin, että Javascript heitti ainakin omassa Windows-ympäristössäni typeErrorin, mikäli sort-metodille annettu funktio ei ollutkaan funktio. Tein samanlaisen tarkistuksen ja poikkeuksen heiton omaSort-funktiolle: ensimmäisen parametrin täytyy olla taulukko. Tähän käytetään Javascriptistä valmiina löytyvää isArray()-tarkistusta. Poikkeuksista lisää alla.
Javascriptissä voi heittää (throw) ja siepata (catch) virheitä (exceptions, errors). Javascriptissä on Error-olio, joka (saattaa) sisältää virheviestin lisäksi muutakin informaatiota. Mikäli virhe, joka aiotaan heittää on selkeästi jotain kuudesta virhetyypistä (EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError), on selkeintä käyttää jotain olemassa olevista Error-olioista. Tästä oli esimerkki ylempänä.
Mikäli haluaa tehdä omia virheitä, saattaa olla hyötyä tehdä Error-oliosta "aliolioita". Error-olio pitää toteutuksesta riippuen sisällään stack, filename tai linenumber-kenttiä, joista voi olla hyötyä. Ne voivat kuitenkin myös aiheuttaa ongelmia, mikäli niitä yrittää käyttää, eikä toteutus niitä tue.
Yksinkertaisuuden ja mahdollisten ongelmien välttämisen vuoksi suosittelen tekemään omat virheet alla olevan esimerkin mukaisesti.
JS BinEsimerkissä luodaan oma virheolio ja käsitellään omat virheet erillään muista virheistä. Mielestäni tämä on selkeää ja tällöin "oikean" virheen sattuessa se huomataan erikseen ja voidaan toimia eri tavalla, esim. lopettamalla ohjelman toiminta.
Javascript on imperatiiviseen ohjelmointiin luotu kieli. Kieleen on lainattu joitain funktionaalisen kielen ominaisuuksia. Puhdasta funktionaalista ohjelmointia halajavan kannattanee suunnata katseensa muihin ohjelmointikieliin kuten Haskelliin. Näissä saa etua kielen erilaisesta syntaksista muiden ominaisuuksien lisäksi.
Javascript kuitenkin jonkin verran mahdollistaa ohjelmoimaan funktionaalisesti. Tilanteesta, ohjelmointitaidoista ja mieltymyksistä riippuen voi käyttää kumpaa tapaa haluaa. Joskus toinen tapa voi olla selkeämpi vaihtoehto ongelman ratkaisemiseksi. Nimettömät funktiot, funktioiden tallentaminen muuttujan arvoksi, funktioiden välittäminen toisen funktion parametriksi ja callback-funktiot ovat ominaisuuksia, joita funktionaaliset kielet hyödyntävät ja joita Javascript tukee. Aikaisemmin esitelty sort-funktio hyödyntää funktion välitystä ja on hyvä esimerkki siitä.
Alla on esimerkki Callback-funktion käyttämisestä:
JS BinSulkeuma muodostuu aina kun funktion sisällä määritellään funktio, joka käyttää ulomman funktion muuttujia. Sisempi funktio näkee ulomman funktion muuttujat ja nämä jäävät muistiin jos niitä käytetään. Tilanteessa, jossa funktion suoritus on päättynyt, voi sulkeamaa yhä hyödyntää, mikäli sisempi funktio on palautettu kutsujalle viitteenä, esimerkiksi:
JS BinFunktion sanoMoi sisältö jää muistiin, koska funktion sisällä on määritelty funktio sanoRuudulle. Tätä voidaan kutsua vielä sanoMoi-funktion suorituksen jälkeenkin. vapaaTeksti-muuttujaa ei pysty muokkaamaan mitenkään sulkeuman syntymisen jälkeen (tämä on sulkeumien hyöty/idea).
Toinen esimerkki sulkeuman käytöstä, jossa simuloidaan olio-ohjelmointikielten tarjoamia piilotettuja kenttiä:
JS Bin"Privaatteihin" muuttujiin ei ole tässäkään tapaukessa mahdollista päästä suoraan käsiksi. Muuttujien hallinta onnistuu "metodi"-funktioiden kautta. Seuraavaksi päästäänkin sitten tarkastelemeen Javascriptin olioita ja perintää.
Ratkaistavan ohjelmointiongelman käsitteiden luonteva mallintaminen onnistuu olioiden avulla. Olio edustaa jotain asiaa tai käsitettä. Javascriptissä ei ole luokkia, vaan oliot luodaan prototyyppien avulla.
Suosituin tapa luoda olioita Javascriptissä on käyttää konstruktorifunktiota. Alla esimerkki olion määrittelystä konstruktorifunktion avulla ja olion "ilmentymän" luonnista:
function Auto(merkki, malli){ this.merkki = merkki this.malli = malli } biili = new Auto('Audi', 'A4')Tällaiset konstruktorifunktiot on syytä nimetä isolla alkukirjaimella erottamaan ne normaaleista funktioista. Konstruktorifunktiota, jossa käytetään this-toimintoa, ei saa kutsua normaalin funktion tapaan, koska tällöin "this" viittaa globaaliin olioon, joka voi aiheuttaa kummallisia bugeja.
Perintä vähentää koodin turhaa kopioimista. Javascriptissä olioiden välinen periytyminen hoidetaan prototyyppien avulla. Käyttämällä funktion call-toiminnallisuutta, pystyy perintää tekemään hyvinkin luokkapohjaisten kielten tyylillä:
JS BinKonstruktorifunktio Rekka kutsuu aluksi konstuktorifunktiota Auto, jolloin saadaan tässä oliossa määritellyt kentät käyttöön myös Rekka-olioille. Rekka-olio laajentaa sitten Auto-oliota lisäämällä oman vaihteita-kentän.
Periaatteena periytymisen hyödyntämisessä on se, että olioon laitetaan vain kyseiselle oliolle yksilölliset muuttujat ja hyödynnetään "yläolion" ominaisuuksia muuten. Näin vältytään koodin kopioimiselta.
Tähän mennessä kaikki on vaikuttanut suht selkeältä. Lisätään sitten tässä vaiheessa soppaan mukaan metodit/aksessorit. Arto Wiklan materiaalista löytyy hyvä ja selkeä esimerkki aiheesta:
JS BinYllä oleva esimerkki on mielestäni paras ja selkein tapa tehdä olioiden välistä perintää.
Esimerkissä tehdään kuitenkin nopeasti ajateltuna turhia asioita: siinä metodit/aksessorit laitetaan Tuotantoelain-olion prototyyppi-olioon sen sijaan, että ne laitettaisiin suoraan olioon. Lisäksi nuo rivit, joissa luodaan olion prototyyppi-olioksi uusi "yläluokan" olio, vaikuttavat turhilta. Olisi selkeämmän näköistä jos esimerkin kirjoittaisi alla olevaan muotoon:
JS BinKaikki näyttäisi testitulosteen mukaan toimivan niinkuin pitääkin. Kaksi asiaa on kuitenkin huonosti:
Eli olion metodit/aksessorit on hyvä laittaa tehokkuusmielessä olion prototyyppiolioon, riippumatta siitä harrastaako perintää tämän olion suhteen. Ja mikäli aikoo hyödyntää instanceof-tarkistuksia, on syytä korjata myös prototyyppiketju kuntoon.
Verkosta löytyy myös materiaalia, joissa "korjataan" lisäksi "aliolion" konstruktoriviite seuraavasti:
Tuotantoelain.prototype = new Elain(); Tuotantoelain.prototype.constructor = Tuotantoelain;
Mikäli korjausta ei tee, osoittaa constructor-kenttä new-lauseen jälkeen Elain-olion konstuktoriin:
(Tuotantoelain.prototype.constructor===Elain) //trueSe, että onko tuolla kentällä enää merkitystä, on eri asia. Mutta joka tapauksessa tuo on hyvä tiedostaa, mikäli innostuu enemmänkin periytymisaiheesta.
Sulkeumien ja perinnän yhdistäminen taitaakin mennä jo aika vaikeaksi. Tuossa jonkinlainen viritys ja lisäkeskustelua aiheeseen liittyen. En välttämättä lähtisi tekemään enää itse tuota toiminnallisuutta, vaan mikäli katsoisin tuon tarpeelliseksi, luottaisin ehkä johonkin Javascript-kirjastoon, joka toiminnallisuuden tarjoaa.