Ohjelmointitekniikka Tyyppiturvallisuuden tavoittelua Javascriptissa muuttujat ovat tyypittömiä, mutta arvoilla on tyyppi. Muuttuja esitellään var -avainsanalla ja muuttujan tyypin arvoa ei erikseen määritellä. Datatyyppejä ovat javascriptissa seuraavat: - Primitiivityypit - String - Number (Huomaa, että kokonais- ja liukulukuja ei ole eroteltu. Numbereihin kuuluu myös erikoisarvot Infinity ja NaN eli not a number ) - Boolean - Komposiittityypit - Object (Mukaanlukien funktiot) - Array - Erikoisdatatyypit - null - undefined Koska javascript on heikosti tyypitetty kieli, tyypityksen pakottamista pidetään huonona tyylinä ja kielen paradigman vastaisena toimintana. Kuitenkin on tapauksia, joissa esimerkiksi syötteenä saadun arvon tulisi olla jotain tiettyä tyyppiä ja javascriptin automaattinen castaus ei tuota haluttua tulosta. Esimerkiksi verkkokaupassa ei yleensä tulisi voida käyttää liukulukuja kappaletavaran ostomäärää syöttäessä. Tällaisissa tilanteissa tyypin tarkistaminen on paikallaan. Javascriptissa on kaksi eri yhtäsuuruusoperaatiota === ja ==, joista ensimmäinen tarkistaa onko arvot samaa tyyppiä ja sen jälkeen vertailee niitä, jälkimmäinen pyrkii muuttamaan ne saman tyyppiseksi ja sen jälkeen vertailee niitä. Yleinen konsensus on, että kahta yhtäsuuruusmerkkiä tulisi välttää ja käyttää mieluummin kolmea. true == 1; //true
true === 1; //false Javascriptin tyyppitarkastukset voisi toteuttaa seuraavasti: // Tarkistaa, onko annettu arvo kokonaisluku. function isinteger(value) { return isnumber(value) && value%1===0; // Tarkistaa, onko annettu arvo numeraali. function isnumber(value) { return typeof(value)==="number" &&!isnan(value); // Tarkistaa, onko annettu arvo taulukko. function isarray(a) { return (a instanceof Array); // Tarkistaa, onko annettu arvo taulukko joka sisältää vain kokonaislukuja. function containsonlyintegers(a) { if (!isarray(a)) return false; for (var i = 0; i < a.length; i++) { if (!isinteger(a[i])) return false; return true; // Tarkistaa, onko annettu arvo boolean. function isboolean(value) { if(value === true value === false){ return true; else return false; // Tarkistaa, onko annettu arvo taulukko joka sisältää vain numeraaleja. function containsonlynumbers(a) { if (!isarray(a)) return false; for(i = 0; i<a.length; i++){ if(!isnumber(a[i])) return false; return true;
// Tarkistaa, onko annettu arvo merkkijono. function isstring(a) { return typeof(a) == "string"; Algoritmit, funktiot ja sulkeumat, poikkeusten käsittely Funktion muuttujat on hyvä määritellä heti alussa vaikka se olisi vain loopin indeksi, koska se näkyy joka tapauksessa kaikkialla funktion sisällä (Tosin, uusimmissa Javascriptin versioissa on let -avainsana, jolla muuttujan näkyvyys voidaan rajata vain tiettyyn ohjelmalohkoon). Muuttujat tulee määritellä var:n avulla, koska globaaleja muuttujia tulee välttää jos niitä ei tarvitse. Tämä selkeyttää toiminnallisuutta ja vähentää riskiä asettaa ja käyttää vahingossa globaaleja muuttujia, vaikka tarkoitus olisi ollut käyttää paikallisia. Javascript ei suoraan tue yksityisiä muuttujia, mutta niitä voidaan simuloida sulkeumien avulla: function Konstruktori(){ var _privaattimuuttuja ="private";//jos muuttujan on tarkoitus olla privaatti, on //se tapana nimetä _ alkuiseksi, vaikka sitä ei olisikaan suojattu this.julkinenmuuttuja ="public"; this.getprivatevar=function(){ return _privaattimuuttuja; var olio = new Konstruktori(); write(olio.julkinenmuuttuja); write(olio._privaattimuuttuja); write(olio.getprivatevar());
Esimerkissä privaattimuuttujaan päästään käsiksi ainoastaan aksessorin GetPrivateVar():n avulla. Koska javascriptissa yksityisten muuttujien simulointi on suhteellisen monimutkaista sekä hieman hackilta vaikuttavaa, suosittelisimme niiden välttämistä, jollei niiden käytölle ole hyvää syytä. Lisäksi yksityisten muuttujien toteutus tällä tavoin rajoittaa javascriptin leimallista dynaamisuutta ja avoimuutta sekä testattavuutta. Käyttäjän antamat syötteet tulisi aina tarkistaa. Ohjelmoijan vastuulla on tarkastaa muuttujien, parametrien ja funktioiden tyyppi. Julkisissa kirjastoissa olisi hyvä tarkistaa tyypit, jotta niitä ei voisi käyttää väärin. Tyypittömyydestä voi joissakin tilanteissa olla hyötyä, joten aina ei tarvitse pyrkiä javamaiseen tyyliin. Voi tehdä funktioita joiden toiminnallisuus riippuu parametrien tyypeistä. Esimerkki tyypittömyyden eduista: function palauta(){ return 5; function tuplaa(a){ return a*2; tuplaa(2); //4 tuplaa(palauta()); //10 tuplaa(2.2); //4.4 tuplaa(true); //2 var luku = prompt( anna luku ); // kun luku tulee käyttäjältä if isnumber(luku) { tuplaa(a);
Funktionaalinen vs imperatiivinen Imperatiivinen ohjelma toimii yleisesti nopeammin kuin funktionaalisesti tehty. Jos on kyse pienemmästä laskennasta niin sen tekeminen funktionaalisesti on usein selkeämpää. Lisäksi funktionaalisuudella voidaan tehostaa koodin uusiokäyttöä ja geneerisyyttä: function foreach(array, action) { for (var i = 0; i < array.length; i++) action(array[i]); foreach(["wampeter", "Foma", "Granfalloon"], print); Lähde: http://eloquentjavascript.net/chapter6.html Toisaalta tiukasti imperatiivisesti toteutettu javascript ohjelma ei hyödynnä kaikkia javascriptin vahvuuksia, joten saman tien voisi ohjelmassaan käyttää jotain toista kieltä. Ohjenuoramme on, että kumpiakin tyylejä voi käyttää, kunhan tietää että mitä käyttää milloin ja kirjoittaa selkeää ohjelmakoodia. Pieni funktionaalinen ohjelmaesimerkki: function laske(funktio, arvo) { return funktio(arvo); var plus5 = function(x) { x = x + 5; return x; var double = function(x) { x = x * 2; return x; write(laske(plus5, 1)); // 6 write(laske(double, 5)); // 10
Sulkeumat Sulkeuma on hyvin vahva ohjelmointitekniikka. Sulkeumalla voidaan rajata funkioiden ja muuttujien näkyvyyttä. Sulkeumia voi hyödyntää kuten kapselointia kätkemällä tietyn komponentin toteutus mutta tarjoamalla käyttörajapinnan. Seuraavassa esimerkissä teelaskuri olioilla on tallessa oman x arvo. // palauttaa sulkeuman, jossa kenttä x sekä kasvataarvoa funktio function teelaskuri() { var x=0; return { 'x': 0, 'kasvataarvoa' : function() { this.x++; var laskuria = teelaskuri(); write("laskuri A = " + laskuria.x); // 0 laskuria.kasvataarvoa(); write("laskuri A = " + laskuria.x); // 1 laskuria.kasvataarvoa(); write("laskuri A = " + laskuria.x); // 2 var laskurib = teelaskuri(); write("laskuri B = " + laskurib.x); // 0 laskurib.kasvataarvoa(); write("laskuri B = " + laskurib.x); // 1 write("laskuri A = " + laskuria.x); // 2 Poikkeukset Try-Catch korkealla tasolla (Sovellus, client-side), Throw matalalla (Työkalut, kirjastot). http://www.devhands.com/2008/10/javascript-error-handling-and-generalbest-practices/ Miksi käyttää Throwia, kun voi käyttää if-elseä? Throw luo Error -olion jonka funktioista voi olla hyötyä virheen korjaamiseksi.
Oliot ja periytyminen Javascriptissä ei ole luokkia, joten ns. klassista perintää kuten Javassa ja C++:ssa ei ole. Javascriptin perintä perustuu prototyyppeihin. Perintää käytetään jäsentämään ohjelmakoodia. Syitä perinnän käyttämiselle ovat muunmuassa koodin uusiokäyttö ja polymorfismi. 3 oliota, eläin, nisäkäs ja lammas, asetetaan nisäkäs perimään eläimen ominaisuudet, sekä lammas perii nisäkkään ominaisuudet. Nyt lampaalla on nisäkkään, sekä elaimen ominaisuudet. function Elain(paino = 5) { this.ika = 0; this.paino = paino; Elain.prototype.asetaPaino = function (maara) {this.paino = maara Elain.prototype.asetaIka = function (maara) {this.ika = maara Elain.prototype.ikaanny = function() {this.ika++; function Nisakas() { this.poikaset = 0; Nisakas.prototype = new Elain(); Nisakas.prototype.synnyta = function() {this.poikaset++ function Lammas() { this.villaa = 100; Lammas.prototype = new Nisakas(); Lammas.prototype.keraaVillaa = function() { var maara = this.villaa; this.villaa = 0; return maara; Lammas.prototype.ikaanny = function() { this.ika++; this.villaa += 10; this.paino += 5;
var lammas = new Lammas(); lammas.asetapaino(50); // paino 50 lammas.asetaika(7); // ikä 7 lammas.synnyta(); // poikaset 1 write(lammas.villaa); // 100 var villaa = lammas.keraavillaa(); write(villaa); // 100 write(lammas.villaa); // 0 lammas.ikaanny(); // villaa 10, ika 8, paino 55 kk(lammas); // Javascriptissä voidaan laittaa lampaalle uusi ikaanny metodi var ikaantyjalammas = new Lammas(); ikaantyjalammas.asetaika(10); write(ikaantyjalammas.ika); // 10 ikaantyjalammas.ikaanny(); write(ikaantyjalammas.ika); // 11 ikaantyjalammas.ikaanny = function() { this.ika += 3; write(ikaantyjalammas.ika); // 11 ikaantyjalammas.ikaanny(); write(ikaantyjalammas.ika); // 14 write(lammas.ika); // 8 lammas.ikaanny(); write(lammas.ika); // 9 kk(ikaantyjalammas); // nyt ikaantyjalampaan prototyypissä on kenttä // ikaanny:function () { // this.ika += 3; // Prototyyppimuuttujat vs instanssimuuttujat Funktiot tulee pääsääntöisesti asettaa prototyyppiin, ennemmin kuin itse olion instanssiin. Tällöin se vie vähemmän tilaa. Kuitenkin esimerkiksi konstruktorissa välitetyt funktiot on syytä tallentaa instanssimuuttujina. Mikäli prototyypeissä on arvoja, toimivat ne oletusarvoina prototyypin
toteuttavalle oliolle. Parametreja perintäketjussa ylöspäin Kun yläluokan konstruktorissa on parametreja, voidaan aliluokan parametrit välittää yläluokalle seuraavasti: function Elain(ika) { this.ika = ika; function Lemmikkielain(ika, nimi) { this.base = Elain; //base muuttujan sijasta voidaan kutsua konstruktoria suoraan: this.base(ika); //Elain.call(this, ika); this.nimi = nimi; Lemmikkielain.prototype = Elain; var nasu = new Lemmikkielain(5, "nasu"); write(nasu.nimi); Prototyyppiketjussa ylöspäinkulkemiseen menee aina aikaa ja siksi pitkiä prototyyppiketjuja kannattaa välttää, jos suorituskyky on kriittinen. Abstraktien funktioiden soveltamista ja simuloimista: Elain = function(paino, kasvufunktio) { this.paino = paino 0 this.kasva = kasvufunktio // function(){throw new Error('not implemented') if (this.kasva === undefined) throw new Error("Elain on abstrakti luokka!"); Elain.prototype.getPaino = function() { write(this.paino) lammas = new Elain("Fatso", function() {this.paino += "o") kroko = new Elain(25, function() {this.paino += 5) lammas.getpaino() lammas.kasva()
lammas.kasva() lammas.getpaino() kroko.getpaino() kroko.kasva() kroko.kasva() kroko.getpaino() weequ = new Elain(1337) // Error weequ.kasva() The catch? Abstraktion käyttö on Javascriptin kohdalla aivan turhaa johtuen kielen dynaamisuudesta. Olioihin voi lisätä funktioita sekä poistaa ja muokata niitä lennosta. Tämän kaltainen abstrakti luokka palvelee parhaimmillaankin vain jonkinlaisena referenssinä. Duck typing Javascriptissä olioita voi siis muuttaa dynaamisesti, eikä se sisällä käsitteitä kuten "luokka", "abstract" tai "interface". Tästä syystä javascriptissä kenttien tarkastukset tulee tehdä dynaamisesti toisin kuin esimerkiksi Javassa, jossa tiedetään että oliot noudattavat luokkamäärittelyä. Esimerkki dynaamisesta tarkastamisesta: function Koira() { Koira.prototype.hauku = function() { write("hau HAU!") function Kissa() { Kissa.prototype.mau = function() { write("mjiau") function can(obj, methodname) { return ((typeof obj[methodname]) == "function"); var dogi = new Koira()
if (can(dogi, "hauku")) { dogi.hauku()