Lueskelin juuri läpi mielenkiintoisen ja sopivan lyhyen kirjan nimeltä JavaScript: The Good Parts (Amazonin Kindle-versio). Se suorittaa ytimekkään ruumiinavauksen JavaScript-kielelle, nostaa esiin sen hyvät puolet sekä varoittaa käyttämästä huonoja ominaisuuksia.

Ajattelin käydä tässä läpi muutaman asian, jotka olivat minulle uusia tai ainakin harmaata aluetta omassa JavaScript-tuntemuksessa.

Prototyypit vs. luokat

JavaScriptissä ei ole luokkia, eikä sitä kannata ajatella luokkiin perustuvana ohjelmointikielenä. Monen ohjelmoijan ensireaktio tuntuu olevan se, että otetaan käyttöön tai kehitetään oma framework, joka simuloi luokkien toimintaa.

Erityisen hämäävää on JavaScriptin new-syntaksi:

var dog = new Dog(...);

New tarkoittaa JavaScriptissä ihan eri asiaa kuin esimerkiksi Javassa tai C++:ssa. Esimerkin Dog ei ole "luokan" nimi, vaikka niin helposti ajattelisi, koska JavaScriptissä ei ole luokkia. Dog on itse asiassa sellaisen funktion nimi, jonka tehtävänä on alustaa uusi objekti. Uuden objektin perintöhierarkia taas muodostuu Dog.prototype -jäsenen kautta. Kaikki Dog-objektit voisi olla määritelty perimään animal-objekti näin:

var Animal = function() { this.noise = 'Unknown'; };
var animal = new Animal();
var Dog = function() { this.noise = 'Bark!'; };
Dog.prototype = animal;

Tällöin "var dog = new Dog()" muodostaa uuden objektin, joka perii kaikki animal-objektin jäsenet, ja jonka omat jäsenet alustetaan Dog-funktiossa.

Perintöhierarkia ei siis lainkaan perustu luokkiin (joita ei ole olemassakaan), vaan prototyypin täytyy aina olla jokin toinen objekti. Jos animal-objektiin nyt lisätään uusia ominaisuuksia, ne näkyvät heti kaikissa dog-objekteissa.

Tämä lisäys tehdään nimenomaan animal-objektiin eikä Animal-funktioon. Tämän eron ymmärtäminen on vaikeaa, jos asiaa yrittää hahmottaa luokkahierarkiana.

Funktiot ja closuret muuttujien piilottajina

Luokattomuuden myötä JavaScriptissä ei myöskään ole perinteistä public/protected/private-erottelua objektien jäsenmuuttujille. Kaikki jäsenet ovat "public"-tyyppisiä, eli niitä voi tarkastella objektien ulkopuolelta vapaasti.

Tämä on omasta mielestäni ihan hyvä asia, koska koodia voi silloin muokata ja virittää aika vapaasti. Joskus suojaamiselle on kuitenkin tarvetta, kun halutaan pakottaa esimerkiksi tietty API-rajapinta tai välttää joitakin tietoturvaongelmia.

Onneksi JavaScriptin funktiot ovat closureihin yhdistettynä erittäin tehokkaita apuvälineitä muuttujien piilottamisessa. Funktion sisällä määritelty muuttuja on täysin suojassa ulkopuoliselta maailmalta.

Aiempaa esimerkkiä jatkaakseni Dog-objektilla voi olla oma "my"-muuttujansa, joka ei näy objektin ulkopuolelle, mutta jota kaikki sen metodit voivat käyttää:

var Dog = function() {
  var my = {};
  return function() {
    this.setName = function(value) { my.name = value; };
    this.getName = function() { return my.name; };
  };
}();

Tässä on siis jippona käyttää yhtä ylimääräistä function()-määritelyä, jonka sisällä kaikki muut funktiot määritellään. Viimeisellä rivillä tuota ylimääräistä funktiota kutsutaan ()-operaattorilla, jolloin Dog saa arvokseen sen palauttaman varsinaisen funktion.

Funktioiden käyttäminen tällä tavoin monitasoisesti lienee tuttua Lisp-sukuisten funktionaalisten kielten ohjelmoijille, mutta C++/Java-taustan omaaville tämä voi vaatia hieman pohtimista, ennen kuin idea avautuu kunnolla.

Puolipisteet rivien lopussa

Puolipisteet ovat JavaScriptissä periaatteessa vapaaehtoisia rivien lopuissa. Kieltä ei ole kuitenkaan suunniteltu niin, että sääntö niiden poisjättämisestä olisi kovin helppo hahmottaa yksiselitteisesti.

Esimerkiksi return-lause voi yllättäen toimia eri tavalla kuin kuvittelisi:

return
{
    name: '...',
    noise: '...'
}

Tuossa esimerkissä funktio palauttaa arvon undefined, koska returnin perään ilmestyy automaattinen puolipiste rivinvaihdon yhteydessä. Lause pitää siis kirjoittaa tässä muodossa:

return {
    name: '...',
    noise: '...'
}

Itse olen päättänyt olla luottamatta automaattisiin puolipisteisiin ja laitan mieluummin puolipisteen jokaisen rivin loppuun. Silloin on ainakin selkästi nähtävissä, mitä lauseen on tarkoitus tehdä, ja virhetilanteessa se on helppo korjata.