5 gyakori JavaScript hiba
Az egyre bonyolultabb kliens-oldali alkalmazások és a Node.js megjelenésével a JavaScript megkerülhetetlenné vált a webes világban, így érdemes megtanulni, mert jó eséllyel akkor is belefutunk valahol, ha épp nem ez a fő profilunk. A nyelv az egyszerűsége ellenére rendelkezik néhány buktatóval a más nyelvhez szokott programozóknak — ebben a posztban öt gyakori problémát gyűjtöttem össze, amikre érdemes odafigyelni, ha el szeretnénk kerülni a későbbi idegeskedést.
1. Blokkszintű scope feltételezése
Mivel sok hasonló nyelv így működik, valamilyen szinten jogos, hogy azt gondoljuk, a JavaScriptben a blokknyitás új scope-ot képez, de ez nincs így. Nézzük meg az alábbi kódot:
for (var j = 0; j < 1000; j++) {
/* valamilyen muvelet */
}
console.log(j);
Mi lesz ennek a kimenete? Joggal gondolhatnánk, hogy undefined
(vagy esetleg valamilyen kivételt dob az egész), de az igazság 1000
. Más nyelvekben a j
változó csak a for loopon belül élne, itt azonban annak végeztével is elérhető marad (ezt angolul variable hoisting néven szokás emlegetni).
2. Véletlen globális változók
Szintén gyakori, az előzőhöz hasonló hiba a var
kulcsszó elhagyása változók deklarálásánál. Ez ahhoz a nem várt következményhez vezethet, hogy az adott változót globálisként deklaráljuk, ami veszélyes, és ráadásul érthetetlennek tűnő hibákat tud okozni. Egy példa erre az alábbi kódrészlet:
function f() {
i = 1;
}
console.log(i);
Az eredmény sajnos 1
.
3. Callback függvények nem szándékos meghívása
A legtöbb JavaScript framework egyik központi eleme az aszinkron, callbackekre alapozó eseménykezelés, így fontos megérteni ennek az alapjait, mégis gyakran látni ehhez hasonló kódot:
window.onload = myFunction();
Vagy éppen:
$(".header").click(doSomething("John"));
Ezek egyikével sem a várt eredményt fogjuk elérni *. Fontos megjegyezni, hogy a myFunction
egy függvény pointer, míg a myFunction()
ennek a meghívása. Így az első példa így nézne ki korrekten:
window.onload = myFunction;
A második nem teljesen triviális, hiszen argumentumunk is van, ez azonban könnyen áthidalható a JavaScriptben szintén gyakran használt névtelen függvényekkel:
$(".header").click(function() {
doSomething("John");
})
* Kivéve persze, ha ezek a függvények függvény pointerrel térnek vissza, de ettől most tekintsünk el.
4. Callback függvény hozzárendelése cikluson belül
Néha előjön az a feladat, hogy egy loopon belül kell callback függvényeket szolgáltatnunk, például ehhez hasonlóan (nem teljesen életszerű, de a lényeg látható):
for (var i = 0; i < 10; i++) {
links[i].click(function() { alert('A(z) ' + i + '. linkre kattintottál!') });
}
Ez azonban nem az elvárt módon fog működni; bármely callback kiváltásakor a A(z) 10. linkre kattintottál!
felirat fog megjelenni. Miért történik ez?
A probléma nyilván abban rejlik, hogy minden alkalommal ugyanazt az i
változót használjuk, nem jön létre új példány minden iterációban (ráadásul ahogy korábban láttuk, a for loopon kívül is teljesen jól elvan). A callback függvények nyilván valamikor a ciklus után futnak, de ekkor az i
értéke már adott. Mi a megoldás?
A legjobb választás egy ún. IIFE (Immediately Invoked Function Expression) létrehozása. Ez egy olyan technika, amellyel a deklarált függvényt azonnal meghívjuk, majd el is dobjuk, így saját scope-ot képezve. Az i
változó értékét ennek a függvénynek fogjuk paraméterként átadni, amely egy új scope-ban önállóan létezik:
for (var i = 0; i < 10; i++) {
(function f(j) {
links[j].click(function() { alert('A(z) ' + j + '. linkre kattintottál!') });
}(i));
}
5. A this
kulcsszó használata
Az utolsó probléma is — nem meglepő módon — a scope-ok problémaköréhez kapcsolódik. A JavaScriptben a this
kulcsszó más programozási nyelvekhez hasonlóan egy pointer az objektum saját példányára. Azonban sajnos ebben az esetben az, hogy this
mire mutat egész pontosan, függ attól, honnan lett az adott függvény meghívva. Egy gyakori eset a setTimeout
függvénnyel demonstrálható.
Az alábbi kódban definiálunk egy Person
objektumot, aminek lesz egy sayName()
függvénye, ami kiírja a konzolra az adott személy nevét. Szeretnénk elérni, hogy 500 milliszekundum elteltével kiíródjon a konzolra a definiált személyünk neve, így az alábbi kódot hívjuk segítségül:
var Person = function(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
}
var sanyi = new Person("Sanyi");
setTimeout(sanyi.sayName, 500);
Legnagyobb csalódásunkra a konzolra semmi sem fog kiíródni. A probléma itt abban keresendő, hogy a sayName
függvényt a setTimeout
függvényből meghívva a this
a böngésző window
változójára fog mutatni. Ennek megoldására ismét egy névtelen függvényt hívhatunk segítségül (de például a jQuery $.proxy is használható erre):
setTimeout(function() { sanyi.sayName() }, 500);
A fent említett problémák legtöbbje elkerülhető, ha áttanulmányozzuk és követjük valamelyik JavaScript style guide-ot (például a Google is készített egyet). A hasonló JavaScript problémák súlyát jól mutatja, hogy a Github egyenesen azt javasolja, hogy egyáltalán ne használjuk, helyette inkább alkalmazzuk a CoffeeScript nyelvet, ami természetesen JavaScriptre fordítható.