A Javascript programozási nyelv

Alprogramok, modulok

Szintaxis

A JavaScript a C-hez hasonlóan csak egy alprogramot ismer, a függvényt, legalábbis ami a kulcsszót illeti. A függvénynek ugyanis nem kötelező visszatérési értéket adnunk, és ebben az esetben az alprogram eljárásként működik.
Igazodva a nyelv script jellegéhez, a Javascript tervezői elhagytak mindent a függvénydeklarációból, ami nem feltétlenül szükséges: nem kell megadnunk visszatérési értéket, sem a paraméterek típusát, csak azok neveit. A deklarációt a function kulcsszó vezeti be. A visszatérési értéket a return kulcsszóval adhatjuk meg. A függvényeket meghívni a nevükkel, és paramétereikkel lehet.
Például a

function min(a, b) { return a < b ? a : b ; }

szintaktikusan helyes megadás, a függvény pedig jól muködik számokra és stringekre egyaránt:

min(6, 5.45) = 5.45 min('A6', 'A7') = A6

Értelemszerűen a futtató rendszer semmilyen típusellenőrzést nem végez. Ha nem írunk return utasítást, vagy a return jobb oldalán nem áll kifejezés, a visszatérési érték undefined lesz. Ugyanígy, a hiányzó paraméterek undefined-ek lesznek, ha túl kevés paraméterrel hívjuk meg a függvényt. A túl sok paraméter nem zavarja, a felesleget az arguments tömb segítségével érhetjük el. Ezen a módon változó hosszúságú paraméterlistát is kezelhetünk, de a függvény fejlécében ezt nem tudjuk jelezni. A destruktúrálható értékadás bevezetésével lehetővé vált, hogy a függvényeknek (látszólag) több visszatérési értékük legyen, lásd. destruktúrálható értékadás.

Expression closures

A Javascript 1.8-as verziójától kezdve az egyszerűbb függvények a lambda-kifejezések szintaxisára hasonlító módon is definiálhatóak. Például: az első helyett a második is írható:

function(x) { return x*x; } function(x) x*x

A két forma ekvivalens, a második forma csak annyiban különbözik, hogy elhagyásra kerültek a kapcsos zárójelek, és a return utasítás.

Függvény függvénybe ágyazása

A függvények egymásba ágyazása megengedett. Például:

function hypotenuse(a, b) { function square(x) { return x*x; } return Math.sqrt(square(a) + square(b)); }

Paraméterátadás

A primitív típusok (number, boolean) érték szerint adódnak át. Az objektumok (ide értve a tömböket, és a függvényeket is), referencia szerint kerülnek átadásra.

Függvény, mint paraméter

Minthogy a paraméter típusa tetszőleges lehet, átadhatunk paraméterként függvényt is, ekkor a függvényre a formális paraméterként megadott nevén hivatkozhatunk. Természetesen tetszőleges számú ilyen függvényparamétert átadhatunk, ügyelnünk kell viszont arra, hogy az aktuális paraméter függvény és a használt formális paraméter függvény paramétereinek száma megegyezzen, hiszen a nyelv ekkor sem ellenőrzi a paraméterek számát, a meg nem adottak undefined értékek lesznek. Például:

function cserel_valtozo (f, x, y){ return f(y,x); } function kisebb(a, b){ return a<b; } BkisebbA = cserel_valtozo(kisebb, a, b); // ekkor azt kapjuk vissza, hogy a "b" értéke kisebb-e "a"-nál
Az arguments tömb

Minden függvényhez a kliens létrehoz tehát egy arguments nevű tömböt, amelyet a függvény belsejéből hívhatunk meg, és mely tartalmazza az aktuális paraméterek értékeit. Ez leginkább akkor hasznos, ha nem tudjuk a függvény deklarálásakor, hány paraméterrel kívánjuk a későbbiekben meghívni. Természetesen ennek a tömbnek is használhatjuk a length paraméterét. A JavaScript 1.4-es változata előtt az arguments még a függvény paramétere volt, ezért a meghívása függvénynév.arguments néven történik.

A következő példában több stringet szeretnénk eggyé összekonkatenálni, ám nem ismerjük előre, hogy hány string elemmel kívánjuk meghívni a függvényt, ezért paraméterként csak az elválasztó elemet adjuk át, a stringeket pedig az arguments tömbön keresztül kérdezzük le.

function concat(separator) { var result = ""; for (var i=1; i< arguments.length; i++) { // arguments.length a stringek száma result = result + arguments[i] + separator; // az eddigiekhez hozzávesszük a stringet és az elválasztóelemet } return result; } concat (".", "egy", "kettő", "három"); // eredménye "egy.kettő.három" lesz

Túlterhelés

Sokan szerették volna, ha az ECMAScript 4-ben bevezetik a túlterheléses függvényeket, hogy JavaScriptben is elérhető legyen, de ezt visszautasították. Ezért túlterhelt függvények bevezetése a Javascriptben továbbra sem lehetséges. Ennek két fő oka van: Egyrészt mivel a paraméterek típusa nincs feltűntetve a függvénydeklarációban, azok alapján nem lehet választani. Másrészt, a paraméterek száma szerint sem lehet túlterhelni, ugyanis egy függvény hívható a specifikáltnál kevesebb paraméterrel (ilyenkor a hiányzó paraméterek értéke undefined lesz), és több paraméterrel is (ilyenkor az arguments tömbben érhetőek el a további paraméterek). Ha a szükségesnél kevesebb paraméterrel hívták a függvényt, a paraméterekhez rendelt alapértelmezett értékek a következő módon szimulálhatóak:

function fun(arg0, arg1){ arg1 = arg1 || "alapértelmezett érték"; ... }

Rekurzió

Természetesen lehetőségünk van rekurzív hívásra, azaz a függvény meghívhatja saját magát is. A tipikus példa erre a faktoriális számolás.

function factorial(n) { if ( n==0 || n==1 ) return 1; else { var result = ( n * factorial(n-1) ); return result; } } factorial(4); // esetén 3 rekurzív hívás történik, az eredmény 24 lesz

Generátorok és iterátorok

Generátorok

A generátorok a JavaScript 1.7-es verziójában bevezetett, speciális módon használható, általában iteratív számítást végző függvények. A generátorok alkalmazása egy új megközelítése annak a gyakori sémának, hogy egy ciklus minden i. iterációjában meghívunk egy "callback" függvényt. Pl. A Fibonacci számok számítása a JavaScript 1.7 előtti verziók eszközeivel kódolva:

function do_callback(num) { document.write(num + "<br>\n"); } function fib() { var i = 0, j = 1, n = 0; while (n < 10) { do_callback(i); var t = i; i = j; j += t; n++; } } fib();

Ugyanezt a feladatot generátor alkalmazásával így is megvalósíthatjuk a nyelv 1.7-es verziója óta:

function fib() { var i = 0, j = 1; while (true) { yield i; var t = i; i = j; j += t; } } var g = fib(); for (var i = 0; i < 10; i++) { document.write(g.next() + "<br>\n"); }

Egy függvény attól válik generátorrá, hogy szerepel benne a yield utasítás. A generátorok általában iteratív számítást végeznek, azaz a yield utasítás egy ciklusmagban található. A generátor függvényszerű meghívásánál (var g = fib(); a példában) csak a paraméterátadás történik meg (ha a fib()-nek volnának paraméterei, itt történne meg), de a függvény törzse még nem kerül végrehajtásra. A függvényszerű meghívás visszatérési értéke egy generátor-iterátor objektum (a példában g). A generátor iteratív számításának részeredményeit ennek a generátor-iterátornak a next() műveletével kérdezhetjük le. A next() első hívásának hatására a generátor törzse elkezd végrehajtódni, egészen az első yield utasításig, ahol az utasítás jobb oldalán álló értékkel visszatér, ez lesz a next() hívás visszatérési értéke. A generátor ekkor a yield utasításnál felfüggesztődik, majd a következő next() hívás hatására ettől a ponttól folytatódik a törzsének végrehajtása. (A yieldre gondolhatunk úgy, mint egy speciális return utasításra, melynek sajátossága, hogy a következő híváskor nem teljesen elölről kezdődik a törzs végrehajtása, hanem a legutóbbi ilyen speciális return után.) Egy generátor tartalmazhat több yield utasítást is, ilyenkor a next() hívás hatására csak a felfüggesztés helyétől a következő yieldig terjedő rész hajtódik végre. Nem szükségszerű továbbá, hogy a generátoron belüli iteráció végtelen legyen, lehet véges is, erre azonban a használat során figyelni kell. Végtelen generátor esetén csak a kliens kódtól függ, hogy meddig akarja használni a generátort: ha már nincs rá szükség, a close() metódussal zárhatjuk le a generátort. Ekkor a generátor belsejében lévő esetleges finally blokkok azért végrehajtódnak. Ha viszont a generátor nem végtelen, akkor a generátorban potenciálisan lévő iteráció ciklusfeltétele hamissá válhat, így tulajdonképp a generátor "végére érhetünk". Másképp megközelítve: ha a generátor törzsében már nem kerülhet a vezérlés yield utasításra, a generátor befejeződik. Ha a generátorra befejezett állapotban hívjuk a next() függvényt, az StopIteration kivételt vált ki. Emiatt egyrészt a véges iterátorokat érdemes try-catch blokkal védeni, másrészt viszont a kivétel explicit jelzést jelent az iteráció végére, melyet ki is használhatunk (lásd. pl. lentebb).
Például: egy véges generátor

function range(a,b){ var i=a; while (i !=b){ yield i; i++; } } var r = range(5,12); print(r.next()); // 5 print(r.next()); // 6 //... print(r.next()); //11 print(r.next()); // [Exc: Stop Iteration] !!!

A generátorokat legpraktikusabban a "for...in" ciklusokban tudjuk használni. Ennek több előnye is van: a kód nem csak tömörebb, és olvashatóbb lesz, de a next() függvények hívása is automatikus lesz. Továbbá a "for...in" konstrukció kifejezetten figyeli ill. igényli a StopIteration kivételt, mint az iteráció végének jelét, vagyis annak kezelésével sem kell foglalkozunk. Például:

for (key in range(5,12)){ print(key); }
Iterátorok

Az előző részben ismertetett generátorok sok tulajdonsága következik abból, hogy iterátorokkal tudjuk őket használni, pl. az, hogy "for...in" ciklusban is használhatunk generátorokat. A "for...in" ciklust eddig abban a formájában ismertük, hogy tömbökön, vagy objektumokon iterálhattunk vele. Ilyenkor a háttérben valójában az adott objektum, ill. tömb iterátorát kérjük el, és azzal végezzük az iterációt. Ezek alapján, pongyolán fogalmazva, a generátorok segítségével a "bejárható adatszerkezetek" körét a függvényekre is kiterjeszthetjük.
Bár érdemes ismerni őket, az iterátorokkal a programozónak explicit ritkán kell foglalkoznia, a "for...in" ciklus szintaktikusan elrejti ezek használatát. Előfordulhat azonban olyan eset, amikor felül akarjuk definiálni az alapértelmezett iterátort, mely minden objektumhoz automatikusan létrejön, és annak mezőit sorolja fel. Ekkor az "__iterator__" (2x aláhúzással az elején, és a végén) függvényt kell felüldefiniálni. Egy objektum iterátorát explicit az "iterator(<objnev>)" függvénnyel lehet lekérdezni.

Predefinit függvények

eval()

Az eval függvény kiértékel egy JavaScript kódot, mely lehet kifejezés, amelyet kiértékel, illetve utasítások sorozata, ekkor azok végrehajtásra kerülnek. Az aritmetikai kifejezésekhez természetesen nem kell használnunk, hiszen azokat a nyelv automatikusan kiértékeli.

isFinite()

Ez a függvény visszaadja egy matematikai kifejezésről, hogy eredménye véges lesz-e. Vagyis hamissal tér vissza, ha a kifejezés eredménye nem szám (NaN), különben igazzal tér vissza. Hamis eredményt hoz például a 0-val való osztás.

isNaN()

Visszaadja, hogy a megadott kifejezés szám-e vagy sem. Itt nem csak számokat, de bármit megadhatunk, és ha a kifejezést nem tudja számmá konvertálni, vagy értelmezhetetlen lesz a szám (például a 0-val való osztás), akkor igazzal tér vissza, különben hamissal.

parseInt() és parseFloat()

A parse függvényekkel stringeket tudunk számmá konvertálni. Igaz, a nyelvben van automatikus típuskonverzió, mégis ha egy szövegbe számon, tizedes ponton, exponensen, illetve előjelen (valamint hexadecimális esetén A-F) kívűl más is kerül, akkor azt már nem tudja a nyelv konvertálni, viszont a parse függvények feldolgozzák az első nemkívánatos karakterig a függvényt, és az eddig megkapott számot adják vissza. A parseFloat valós, míg a parseInt egész számmá próbál konvertálni. A parseInt esetében megadhatunk egy második paramétert is, hogy milyen számrendszerbeli számmá kívánjuk konvertálni az első paramétert. Ez lehet decimális, oktális és hexadecimális. Ha nem adunk meg paramétert, akkor decimális számrendszerben dolgozik.
Például:

parseint("17A3"); // az eredemény 17 lesz, A-t már nem tudja feldolgozni parseint("17A3", 16); // az eredmény 17A3 lesz hexadecimális számrendszerben
Number() és String()

E két függvény bármely tetszőleges objektumot számmá, illetve szöveggé próbál konverálni, például ha egy dátumot szeretnénk szövegként, illetve számként kiíratni:

D = new Date (430054663215); String(D); // eredménye "Thu Aug 18 04:37:43 GMT-0700 (Pacific Daylight Time) 1983" Number(D); // eredménye 430054663215
escape() és unescape()

Segítségükkel tudjuk kódolni, illetve dekódolni a Unicode szöveget hexadecimális formában. Az escape függvény szövegből kódot állít elő, míg az unescape a kódból állít elő szöveget. Leginkább a serverek használják az URL-ek kezelésére.

Hoisting

A láthatóságnál láttuk, hogy egy függvényen belül deklarált változó hatóköre kiterjed az egész függvényre, függetlenül attól, hogy blokkban van-e, vagy sem. Ez azért van így, mert a JavaScript nem blokkalapú láthatósággal rendelkezik, hanem függvényalapúval. Ez azt jelenti, hogy a JS interpreter a függvényen belül deklarált változókat és függvényeket felülre emeli (Hoisting) és undefined értékkel deklarálja őket, és az aktuális sorban csak értékadás történik.

Variable Hoisting

A változók felemelése a függvény első sorába viszonylag egyszerűen történik. Ha függvény belsejében létrehozunk egy változót, például var i = 0; akkor hoisting esetén a függvény első sorában var i; lesz felvéve, aminek undefined az értéke. Az adott sorban i = 0; értékadás történik, így látható, hogy az első sor után az i változó már látható. Ha egy változó még nem lett deklarálva, de megpróbáljuk kiirítni az értékét, akkor kivétel dobódik, és ha nem kezeljük le, akkor elszáll a program.

function hoisting(){ // i deklarálva van, de értéke undefined console.log(i); // j nincs deklarálva, és exception dobódik, és elszáll a program console.log(j); var i = 0; // az értékadás megtörtént, i értéke 0 console.log(i); } hoisting();

Function Hoisting

Függvényen belül definiált függvények esetén is történik emelés. Mielőtt megnéznénk, tisztáznunk kell, hogy javascriptben többféle függvény deklaráció lehetséges. Az egyik az angolul Function Declaration-nek, vagy magyarul Függvény Deklarációnak nevezett "technika", function függvényNév() { .. }. A másik módszer a Function Expression (Függvény kifejezés), amikor egy hivatkozott névvel (változó) látjuk el a függvényt. var függvényNév = function() { .. }. A két esetben különbözőképp működik az emelés.

Névterek

Nincs beépített kulcsszó névterekre, helyette csomagoló objektumokat hozhatunk létre var myNameSpace = myNameSpace || {}; formában. Alnévtereket is létre tudunk hozni mivel egy objektum egy adattagja újabb objektum is lehet. Ezt kihasználva myNameSpace.subNameSpace = {}; alakban felvehetünk alnévtereket.
A with kulcsszóval minősítő nevek nélkül tudjuk használni a névterekben felvett függvényeket. Példa:

var myNameSpace = myNameSpace || {}; myNameSpace.subNameSpace = {}; myNameSpace.subNameSpace.myFunction = function(){ console.log("Névterek :)"); } //minősítőnevekkel: myNameSpace.subNameSpace.myFunction(); //Névterek :) // vagy with kulcsszóval: with(myNameSpace.subNameSpace){ myFunction(); //Névterek :) //console.log(max(2,3,4,5)); //max is not defined with(Math){ console.log(max(2,3,4,5)); //5 } }