Ebben a fejezetben a JavaScript nyelv főleg azon tulajdonságait tekintjük át, amelyben a C-alapú nyelvektől különbözik. Ez a fejezet elsősorban kitekintés, a benne lévő ismeretekre alapvetően nincsen szükség a tananyag megértéséhez, de nagyon ajánlott megismerkedni vele azoknak, akik szeretnének elmélyedni a JavaScript nyelvben. A fejezet végén a JavaScript beépített objektumairól és az időzítőkről van szó, amit mindenkinek illik ismernie.
Az előző fejezetben áttekintettük a JavaScript nyelv alapjait főleg olyan szempontból, hogy milyen hasonlóság is fedezhető fel a C++ nyelvvel. Ebben a fejezetben egyrészt kiegészítjük az eddigieket, másrészt újabb ismereteket tekintünk át.
A JavaScript számos globális elérhető függvényt definiál. A következőkben ezek közül tekintjük át a fontosabbakat. A teljes listát a szabvány és a referencia tartalmazza.
Ha egy matematikai művelet eredménye kivezet a számok halmazából, akkor a JavaScript egy speciális számot ad eredményül, ami igazából nem-szám, a NaN (Not a Number) értéket. A NaN toxikus hatású, azaz matematikai kifejezésben megjelenve, az is NaN-t ad eredményül. Egyedüli biztos vizsgálata az isNaN() függvénnyel lehetséges.
var sokPenz = 100 * 'kevés pénz'; sokPenz; // => NaN sokPenz / 10; // => NaN isNaN(sokPenz); // => true
Ha az ábrázolható értéktartományon kívülre vezet egy matematikai műveletek, akkor két speciális számot kaphatunk, a pozitív végtelent (Infinity vagy +Infinity) vagy a negatív végtelent (-Infinity). Vizsgálata az isFinite() függvénnyel lehetséges.
var a = 10/0; a; //Infinity isFinite(a); //false
Szöveges érték explicit átalakítása számmá a parseInt() és a parseFloat() globális függvényekkel lehetséges. Mindkét függvény első paramétereként az átalakítandó szöveget kell megadni. A parseInt()-nek e mellett második paraméterként azt is meg lehet adni, hogy a szöveget milyen számrendszerű számnak értelmezze. Érdemes ezt a paramétert mindig kitölteni.
Mindkét függvény addig próbálja értelmezni a szöveget, amíg a formátumának megfelelő számokat talál az elején. Ha a szöveg egyáltalán nem alakítható át, akkor NaN lesz az eredmény.
//parseInt parseInt('123'); //123 parseInt('123', 10); //123 parseInt('0101', 2); //5 parseInt('alma', 10); //NaN parseInt('5alma', 10); //5 //parseFloat parseFloat('4.54'); //4.54 parseFloat('3.1415 a pi'); //3.1415
Webes alkalmazásokban az információ gyakran kerül tárolásra vagy továbbításra szöveges formátumban. Annak érdekében, hogy a speciális karakterek se okozzanak gondot, megfelelően kódolni szükséges őket. JavaScriptben az encodeURI() és encodeURIComponent() függvények szolgálnak egy kódolatlan szöveg kódolására, és a decodeURI() és decodeURIComponent() függvények egy kódolt szöveg visszanyerésére. Az encodeURI() és decodeURI() függvények teljes URI-k kódolására szolgálnak, feltételezvén, hogy bizonyos karakterek (/, :, ;, ?, #, &) az URI részei, ezért ezeket kihagyják a kódolási folyamatból. Ezzel szemben az encodeURIComponent() és decodeURIComponent() függvények az URI egyes részeinek kódolására valók, ezeknél a fent említett speciális karaktereknek nincs speciális funkciója, azok a szöveg részét képezik.
//Szöveg kódolása és dekódolása var kod = encodeURIComponent('árvíztűrőtükörfúrógép'); kod; //"%C3%A1rv%C3%ADzt%C5%B1r%C5%91t%C3%BCk%C3%B6rf%C3%BAr%C3%B3g%C3%A9p" var dekod = decodeURIComponent(kod); dekod; //"árvíztűrőtükörfúrógép" //URI kódolása encodeURI('http://példa.hu/index.php?sz=Bogyó és Babóca'); //"http://p%C3%A9lda.hu/index.php?sz=Bogy%C3%B3%20%C3%A9s%20Bab%C3%B3ca" //URL küldése paraméterként var url = encodeURIComponent('http://példa.hu/index.php?sz=Bogyó és Babóca'); //"http%3A%2F%2Fp%C3%A9lda.hu%2Findex.php%3Fsz%3DBogy%C3%B3%20%C3%A9s%20Bab%C3%B3ca" encodeURI('http://valami.hu/index.php?hova=') + url; //"http://valami.hu/index.php?hova=http%3A%2F%2Fp%C3%A9lda.hu%2Findex.php%3Fsz%3DBogy%C3%B3%20%C3%A9s%20Bab%C3%B3ca"
Szövegek oda-visszakódolásánál találkozhatunk még az escape() és unescape() függvényekkel is. Ezeket azonban a szabvány már nem tartalmazza, elavultak, és használatuk nem javasolt.
Az előző fejezetben láthattuk, hogy JavaScriptben hogyan lehet függvényeket létrehozni. Akkor főleg olyan szemmel néztük meg őket, hogy a C alapú nyelveknél megismert függvényeket hogyan tudjuk JavaScriptben megvalósítani. Láthattuk, hogy a szintaxis ebben az esetben eltérő az ott megismertektől, ami főleg a JavaScript nyelv gyengén típusosságából adódik.
A JavaScriptbeli függvények azonban sokkal sokoldalúbbak, amelyek a nyelv funkcionális oldalát biztosítják. A következőkben áttekintjük a JavaScript függvények néhány további tulajdonságait.
Egy függvény meghívása úgy történik, hogy a neve után gömbölyű zárójelben megadunk valahány paramétert (akár egyet sem). Ha azonban nincsen zárójel a függvény neve után, akkor csak hivatkozunk a függvényre, azaz függvényreferenciát használunk. Egy függvényreferencia sok helyen megjelenhet (ld. lentebb a függvényliterálnál, vagy később az időzítőknél és eseménykezelőknél), legegyszerűbb esetben egy függvényt másképpen nevezünk el, ahogy az alábbi példa is mutatja:
//Függvénydeklaráció function ketszerez(a) { return a * 2; } //Függvényreferencia átadása var duplaz = ketszerez; //Függvény meghívása duplaz(21); //42
Itt a duplaz változó megkapta ugyanannak a függvénynek a referenciáját, mint amire a ketszerez mutatott. Így a duplaz-t meghívva a ketszerez() függvény fut le.
Az aktuális és formális paraméterek száma nem kell, hogy megegyezzen. Ha kevesebb aktuális paramétert adunk meg, akkor az értéket nem kapó formális paraméterek értéke undefined lesz. Ha több aktuális paramétert adtunk meg, mint ahány formálisat soroltunk fel, akkor a függvényen belül egy arguments nevű tömbszerű objektumon keresztül lehet őket elérni. Az aktuális paraméterek számát ennek length tulajdonságával lehet lekérdezni. Visszatérési érték hiányában a függvény undefined-dal tér vissza.
function proba(a, b) { console.log('a =', a); console.log('b =', b); console.log('arguments =', arguments); } //Aktuális és formális paraméterek száma megegyezik proba(1, 2); // => a = 1 // => b = 2 // => arguments = [1,2] //Kevesebb aktuális paraméter proba(1); // => a = 1 // => b = undefined // => arguments = [1] //Több aktuális paraméter proba(1, 2, 3) // => a = 1 // => b = 2 // => arguments = [1,2,3] //return hiányában a visszatérési érték undefined console.log(proba(1, 2)); // => undefined
Az arguments tömb lehetőséget ad általános függvények írására is. Tekintsünk egy olyan függvényt, amely a paraméterül megkapott számok átlagát adja vissza.
//Az átlag függvény definíciója function atlag() { var s = 0; var n = arguments.length; for (var i = 0; i < n; i++) { s += arguments[i]; } return s / arguments.length; } //Az átlag függvény használata atlag(8, 9); //8.5 atlag(1, 2, 3, 4); //2.5 atlag(); //NaN
A függvényeket eddig olyan szempontból vizsgáltuk, hogy hogyan lehet őket létrehozni, azaz deklarálni, és meghívni. Ennek során megismerkedtünk a függvénydeklarációval, ami a függvénylétrehozás fent használt módszere.
JavaScriptben azonban a függvényeket az teszi sokoldalúvá, hogy a függvény önmagában egy kifejezés, és mint ilyen, más kifejezésekben megjelenhet. Mivel a többi adattípushoz tartozott egy olyan forma, amellyel kifejezésekben megjelenhetnek – ezt neveztük literálformának –, akképpen a függvényekhez is tartoznia kell egy literálformának, ha ő is kifejezésekben szerepelhet. Ez a függvényliterál, és a következőképpen néz ki:
function (par1, par2) { //JavaScript kód }
A függvényliterált nevezzük még függvénykifejezésnek és névtelen függvénynek is.
A függvény kifejezés mivolta azt jelenti, hogy bármilyen kifejezésben megjelenhet. Ezt úgy is nevezik, hogy a függvény első osztályú objektum. Legegyszerűbb ezt úgy elképzelnünk, hogy ahol például egy számliterált használnánk, oda függvényliterált is írhatunk. Ekként egy függvényliterál dinamikusan bárhol létrehozható, és szerepelhet például
////////////////////////////////////////// // Számliterál különböző kifejezésekben // ////////////////////////////////////////// //Értékadás var a = 42; //Objektum adattagja var obj = { mezo1: 42 }; //Függvényparaméter function fv(par) { console.log(par); } fv(42); //Függvény visszatérési értéke function fv() { return 42; } ////////////////////////////////////////////// // Függvényliterál különböző kifejezésekben // ////////////////////////////////////////////// //Értékadás var a = function(a, b) { return a + b; }; //Objektum adattagja var obj = { mezo1: function(a, b) { return a + b; } }; //Függvényparaméter function fv(f, a, b) { console.log(f(a, b)); } fv(function(a, b) { return a + b; }, 10, 32); //Függvény visszatérési értéke function fv() { return function(a, b) { return a + b; }; }
Ha a függvényliterált értékadásban használjuk, akkor a függvénydeklaráció mellett megjelenik a függvények egy másik létrehozási formája is, amely függvénykifejezést használ. A kétféle létrehozási forma apróbb eltérésekkel – amelyekre most nem térünk ki – egyenértékű. Hívásukban különbség nincs.
//Függvénydeklaráció function osszead(a, b) { return a + b; } //Függvénykifejezéssel var osszead = function (a, b) { return a + b; }; //Hívásuk osszead(10, 32); //42
A függvény mint kifejezés megjelenhet függvények paramétereként is, amellyel igen sokoldalú és általános függvények készíthetők. Ekkor a függvény meghívhatja a paraméterként megadott függvényt, így a függvénynek logika adható át a paraméterként megjelenő függvény segítségével.
Ennek segítségével például könnyen általánosíthatjuk azokat a programozási tételeinket, amelyek valamilyen T tulajdonságú elemmel dolgoznak. Itt a T egy olyan függvény, amely a tételben általánosan jelenik meg, és a konkrét feladattól függően fejthető ki. Az előző részben leírt lineáris keresés például így írható át:
function kereses(x, T) { var i = 0; while (i < x.length && !T(x[i])) { i++; } return { vane: i < x.length, sorsz: i }; }
Ha negatív elemet szeretnénk keresni számok sorozatában, az így nézhet ki:
function negativE(p) { return p < 0; } var tomb = [1, 3, -2, 8]; console.log(kereses(tomb, negativE));
Ha páratlan szám keresése a cél, akkor csak a T függvényt kell átírnunk.
function paratlanE(p) { return p % 2 !== 0; } var tomb = [1, 3, -2, 8]; console.log(kereses(tomb, paratlanE));
Igen összetett feladatok oldhatók meg azokkal a függvényekkel, amelyek függvényeket adnak vissza. Ennek részletesebb tárgyalása nem tartozik e tananyag keretei közé, példaképpen azonban nézzünk meg egy egyszerű esetet, amely során egy függvény a karakterként megadott operátor alapján a megfelelő műveletet elvégző függvényt adja vissza:
function muveletKeszito(op) { if (op === '+') { return function (a, b) { return a + b; }; } else if (op === '*') { return function (a, b) { return a * b; }; } } //Összeadó függvény készítése var muvelet = muveletKeszito('+'); muvelet(10, 32); //42 //Szorzó függvény készítése var muvelet = muveletKeszito('*'); muvelet(10, 32); //320
A függvényliterál minden esetben helyettesíthető függvényreferenciával. Ez azt jelenti, hogy nem kell minden esetben a függvény felhasználási helyén megjelennie a névtelen függvénynek. Azt külön definiálhatjuk máshol – vagy függvénydeklarációval, vagy függvénykifejezéssel –, és az adott helyen csak hivatkozunk a függvényre a nevén keresztül. Ez fentebb már többször előjött, például a Hivatkozás és meghívás vagy a Függvény mint paraméter fejezetben. Az előző fejezetbeli muveletKeszito() függvény kódját ezzel olvashatóbbá tehetjük úgy, hogy a két visszaadandó függvényt külön definiáljuk, és visszaadáskor csak hivatkozunk rájuk:
function osszead(a, b) { return a + b; } var szoroz = function (a, b) { return a * b; } function muveletKeszito(op) { if (op === '+') { return osszead; } else if (op === '*') { return szoroz; } }
A fenti példában szándékosan definiáltuk különbözőképpen az osszead() és szoroz() függvényt, demonstrálandó azt, hogy a hivatkozás független a létrehozás módjától.
Érdekességképpen megemlíthetjük, hogy a függvényliterál és -hivatkozás felcserélésének van még egy érdekes következménye. Függvény hívásakor a függvényhivatkozás után gömbölyű zárójelet kell tenni. A függvényhivatkozás azonban helyettesíthető a függvényliterállal, azaz a függvény helyben definiálásával. Ekkor a definiált függvényt rögtön meg is hívjuk, a szakirodalom az ilyen függvényeket önkioldó függvényeknek nevezi.
//Függvény definiálása var szoroz = function (a, b) { return a * b; } //Függvény meghívása szoroz(6, 7); //42 //vagy hangsúlyozandó, hogy itt függvényreferencia van (szoroz)(6, 7); //42 //Függvényhivatkozást függvényliterállal helyettesítve (function (a, b) { return a * b; })(6, 7); //42
Önkioldó függvényeket sok helyen alkalmazzák, de ennek részletes tárgyalása nem ennek a tananyagnak része.
Az előző fejezetben a JavaScript objektumokról megállapítottuk, hogy kulcs-érték párok gyűjteménye. Ott elsősorban olyan szemmel tekintettünk rájuk, hogy milyen hasonlóság fedezhető fel más nyelvekkel összevetve, és megállapítottuk, hogy leginkább a rekordnak (a C++-beli struct-nak) feleltethetők meg. Természetesen jó pár különbségre is fény derült. A JavaScript objektumok abszolút dinamikusak, értékei tetszőlegesen módosíthatók, bővíthetők, törölhetők.
Az alábbiakban a JavaScript objektumok további tulajdonságaival ismerkedünk meg, és látni fogjuk, milyen fontos szerepet töltenek be a nyelvben.
A kulcs-érték párokról megjegyeztük, hogy a kulcs alapvetően tetszőleges azonosító lehet; ha azonban az azonosító foglalt kulcsszó vagy nem felel meg az azonosító szintaktikai szabályainak, akkor szövegként kell megadnunk. Az értékek elemi vagy összetett adattípusok is lehetnek, ezeket tulajdonságoknak hívjuk. Nem beszéltünk azonban arról, hogy érték lehet függvény is, ebben az esetben metódusról beszélünk.
A JavaScript objektumok tehát tulajdonságok és metódusok gyűjteménye, és ilyen szempontból sokkal jobban hasonlítanak az objektum-orientált nyelvek objektumaira, amelyek ugyancsak adattagokat és metódusokat tartalmaznak.
A metódusokat egyszerűen úgy adjuk meg, hogy az érték helyére egy függvényliterált írunk. A metódus meghívása az objektum megfelelő kulcsán keresztül megkapott függvény meghívásából áll.
//Objektum létrehozása var matyi = { kor: 1.5, nev: 'Mátyás', sir: function () { console.log('Oáááá'); } }; //Objektum adattagjainak elérése matyi.kor; //1.5 matyi.nev; //"Mátyás" //Objektum metódusának meghívása matyi.sir(); //"Oáááá"
A metódusoknak természetesen lehetnek paraméterei is. Tegyük fel, hogy matyi beszélni tanul, és visszamond mindent, amit hall.
//matyi objektum bővítése a beszel metódussal matyi.beszel = function (szo) { console.log(szo); } //A beszel metódus meghívása matyi.beszel("csacsi"); //"csacsi" matyi.beszel("pillangó"); //"pillangó"
Egy metóduson belül az objektumra hivatkozni a this kulcsszóval lehet. Ezen keresztül elérhetőek az objektum tulajdonságai és metódusai. Egészítsük ki a matyi objektumot egy bemutatkozas() metódussal, amely kiírja a konzolra a nevet.
//matyi objektum bővítése a bemutatkozas metódussal matyi.bemutatkozas = function() { console.log(this.nev + " vagyok.") } //Az új metódus meghívása matyi.bemutatkozas(); //"Mátyás vagyok"
JavaScriptben az objektumok különösen fontos szerepet játszanak. A vezérlési szerkezeteken kívül tulajdonképpen minden objektum. Még a függvények is objektumok, sőt az elemi adattípusok is tudnak objektumként viselkedni, ahogy majd lentebb látjuk.
Az ECMAScript szabvány a következő típusokat különbözteti meg:
Az első öt típus az előző fejezetből ismerős lehet. Ezek az elemi adattípusaink. Minden további összetett típus az objektum alá tartozik. Így például a függvény egy olyan objektum, amelynek futtatható a kódja. A tömb olyan objektum, amelynél a kulcsok egész típusúak és a length paraméternek kitüntetett szerepe van, mert az mindig eggyel nagyobb, mint a legnagyobb egész index.
A nyelvnek azon tulajdonsága, hogy szinte minden objektum, párosulva azzal, hogy ezek az objektumok dinamikusak, nagyon sokoldalúvá teszi a nyelvet. Ennek részleteibe ez a tananyag nem megy bele, csupán felvillant pár érdekes gondolatébresztő példát.
Elemi adattípust úgy tudunk objektumként használni, hogy az értéket tároló változó neve mögé pontot leírva valamelyik kulcsára hivatkozunk. Ilyenkor az értelmező az elemi adattípust objektummá alakítja, más szóval becsomagolja. A három elemi adattípusnak, a logikainak, a számnak és a szövegnek három csomagolóobjektuma van, a Boolean, a Number és a String. Ez utóbbiak lentebb ki lesznek fejtve, most csak nézzünk egy példát az elemi adattípusok objektumként való használatára. A szövegeknek például számos metódusa létezik, amelyekkel az adott szöveg feldolgozható. Ha például egy szöveget nagybetűssé szeretnénk alakítani, akkor a toUpperCase() metódust kell meghívnunk.
var sz = 'alma'; sz.toUpperCase(); //"ALMA" //vagy egyben 'alma'.toUpperCase(); //"ALMA"
Ha már nincsen szükség a csomagolóobjektumra, akkor az értékek újra elemi adattípusként lesznek ábrázolva. Ennek például az a következménye, hogy például egy primitív adattípust ugyan bővíthetünk további tulajdonságokkal, de ezeket visszanyerni nem tudjuk, mert közben a csomagolóobjektum, amihez felvettük azt, megszűnik.
var a = 42; a.ujadattag = 'valami'; a.ujadattag; //undefined
A JavaScriptben háromféle objektumtípust különböztetünk meg:
A következőkben először a felhasználói, majd a beépített objektumokkal fogunk foglalkozni.
Objektumok létrehozásával már az előző részben és most is foglalkoztunk. Láthattuk, hogy új objektumot létrehozni az objektumliterállal lehet, amibe aztán tetszőleges adattagot és metódust felvehetünk. Tekintsük például az alábbi david objektumot, amelybe felvettük ennek a gyermeknek a nevét és korát, valamint két cselekvéséhez tartozó metódust.
//david objektum létrehozása var david = { nev: 'Dávid', kor: 4, bemutatkozik: function () { console.log('A nevem: ' + this.nev); }, alszik: function () { console.log('Zzzzzz...'); } }; //Használata david.nev; //"Dávid" david.bemutatkozik(); //"A nevem: Dávid" david.alszik(); //"Zzzzzz..."
Mit csináljunk akkor, ha még egy zsofi objektumra is szükségünk van ugyanilyen adattagokkal és metódusokkal, csak az adattagok értékei mások? Egyik lehetőség az, hogy lemásoljuk a david objektumot, és átírjuk az adattagok értékeit. Könnyen beláthatjuk, hogy ez a módszer sok hasonló objektum esetében nem túl hatékony: a kód hossza megnövekszik, a másolások számának növekedtével pedig a hibázás lehetősége is egyre nagyobb lesz.
A másik lehetőség, hogy bevezetünk egy olyan függvényt, ami ilyen felépítésű objektumokat készít, és ad vissza. Az ilyen függvényeket objektumgenerálóknak nevezzük.
//Objektumgeneráló függvény function gyerek(nev, kor) { return { nev: nev, kor: kor, bemutatkozik: function () { console.log('A nevem: ' + this.nev); }, alszik: function () { console.log('Zzzzzz...'); } }; } //Használata var david = gyerek('Dávid', 4); var zsofi = gyerek('Zsófia', 7); david.bemutatkozik(); //"A nevem: Dávid" zsofi.bemutatkozik(); //"A nevem: Zsófia"
A JavaScriptben lehetőség van ugyanennek a funkcionalitásnak az elérésére más módon, mely szándékosan a C++ vagy a Java nyelv példányosítási mechanizmusához hasonlít. Ebben az esetben ún. konstruktorfüggvényeket definiálunk, és a new operátor segítségével hozzuk általuk létre az objektumainkat. A konstruktorfüggvényen belül az adattagokat és a metódusokat a this objektumhoz kell felvenni. A konstruktorfüggvényeket hagyományosan nagy kezdőbetűvel írjuk, így megkülönböztetendő a többi függvénytől.
//Konstruktorfüggvény function Gyerek(nev, kor) { this.nev = nev; this.kor = kor; this.bemutatkozik = function () { console.log('A nevem: ' + this.nev); }; this.alszik = function () { console.log('Zzzzzz...'); }; } //Használata var david = new Gyerek('Dávid', 4); var zsofi = new Gyerek('Zsófia', 7); david.bemutatkozik(); //"A nevem: Dávid" zsofi.bemutatkozik(); //"A nevem: Zsófia"
Az utolsó két megoldásnak közös hibája az, hogy ahány objektumot létrehoznak, annyi új függvény is létrehozásra kerül a metódusaiknál. Ennek a problémának egyik megoldása az lehet, hogy a függvényeket külön kiemeljük, és az objektumok létrehozásánál csak hivatkozunk rájuk. Az objektumliterálos létrehozásnál ez a következőképpen nézhet ki:
//Függvények kiemelve function bemutatkozik() { console.log('A nevem: ' + this.nev); } function alszik() { console.log('Zzzzzz...'); } //Objektumgeneráló függvény function gyerek(nev, kor) { return { nev: nev, kor: kor, bemutatkozik: bemutatkozik, alszik: alszik }; } //Használata var david = gyerek('Dávid', 4); var zsofi = gyerek('Zsófia', 7); david.bemutatkozik(); //"A nevem: Dávid" zsofi.bemutatkozik(); //"A nevem: Zsófia"
Ennek a megoldásnak a hátránya, hogy sok függvénnyel terheli a globális névteret.
A másik megoldás a sok függvény problémára a nyelv prototípusosságában rejlik. JavaScriptben minden objektumban van egy rejtett hivatkozás egy másik objektumra, amit az objektum prototípusának nevezünk.
Ennek a prototípus-objektumnak ugyancsak lehet prototípusa, majd annak is, stb., kialakítva így egy prototípus-láncolatot. Ha az objektum egy kulcsára hivatkozunk, akkor az értelmező először a megszólított objektumnál nézi meg, hogy a keresett kulcs megtalálható-e. Ha igen, visszaadja az alatta tárolt értéket. Ha nem, akkor megnézi a prototípus-objektumnál, és így tovább, végigvizsgálva a prototípus-láncolatot.
Ha tehát minden létrehozott objektumnak ugyanaz az objektum a prototípusa, és ebben vesszük fel a metódusokat, akkor a metódusfüggvények csak egyszer jönnek létre, mégis mindegyik objektum eléri őket a prototípus-láncolaton keresztül. Ezt a viselkedést úgy tudjuk elérni, hogy a konstruktorfüggvények prototype tulajdonsága alá vesszük fel a metódusokat. A new meghívásakor az újonnan létrejövő objektum prototípusa automatikusan a konstruktorfüggvény prototype tulajdonságára mutat.
function Gyerek(nev, kor) { this.nev = nev; this.kor = kor; } Gyerek.prototype.bemutatkozik = function () { console.log('A nevem: ' + this.nev); }; Gyerek.prototype.alszik = function () { console.log('Zzzzzz...'); }; var david = new Gyerek('Dávid', 4); var zsofi = new Gyerek('Zsófia', 7); david.bemutatkozik(); //"A nevem: Dávid" zsofi.bemutatkozik(); //"A nevem: Zsófia"
Tulajdonságok újrahasznosításának egyik formája az öröklés, mely során a leszármazott objektum automatikusan megkapja a szülő objektum tulajdonságait. JavaScriptben ezt legegyszerűbben úgy érhetjük el, hogy a gyerek objektum prototípusában felveszünk egy szülő objektumot. Ha például óvodásokat szeretnénk létrehozni, akik gyerekek, de van jelük, és ezt el is tudják mondani, akkor az Ovodas függvényünknek a Gyerek függvényből kell származnia.
function Ovodas(nev, kor, jel) { this.base = Gyerek; this.base(nev, kor); this.jel = jel; } Ovodas.prototype = new Gyerek(); Ovodas.prototype.miAJeled = function () { console.log('A jelem: ' + this.jel); } var zsofi = new Gyerek('Zsófia', 7); zsofi.bemutatkozik(); //"A nevem: Zsófia" var david = new Ovodas('Dávid', 4, 'perec'); david.bemutatkozik(); //"A nevem: Dávid" david.miAJeled(); //"A jelem: perec"
Ugyanezt a funkcionalitást elérhetjük konstruktorfüggvények nélkül is. Az ECMAScript 5-ös szabványa bevezetett egy Object.create() metódust, amellyel lehetővé válik egy új objektumnak egy másik objektumot prototípusának beállítani. Ennek részletes taglalása azonban nem fér e tananyag keretei közé.
A JavaScript nyelvbe számos előre beépített objektum van, amelyekkel gyakori feladatok elvégzése válhat könnyűvé.
A Number objektum (igazából függvény) a számok csomagolóobjektuma. Általa a számokon meghívható néhány speciális formátumba alakító metódus:
//Számok mint objektumok metódusai var a = 12.345; a.toExponential(); //"1.2345e+1" a.toExponential(2); //"1.23e+1" a.toExponential(5); //"1.23450e+1" a.toFixed(); //"12" a.toFixed(2); //"12.34" a.toFixed(5); //"12.34500" a.toPrecision(); //"12.345" a.toPrecision(2); //"12" a.toPrecision(5); //"12.345" a = 42; a.toString(); //"42" a.toString(2); //"101010" a.toString(5); //"132"
A Number objektumnak önmagában is van pár hasznos tulajdonsága:
//A Number objektum tulajdonságai Number.MAX_VALUE; //A legnagyobb ábrázolható pozitív szám Number.MIN_VALUE; //A legkisebb ábrázolható negatív szám Number.NaN; //NaN Number.NEGATIVE_INFINITY; //-Infinity Number.POSITIVE_INFINITY; //Infinity
A String objektum (ez is függvény) a szövegek csomagolóobjektuma, és jó pár hasznos metódust tesz elérhetővé a szövegpéldányokon. A teljesség igénye nélkül nézzünk néhány hasznos függvényt:
'piros alma'.charAt(2); //"r" 'piros alma'.charCodeAt(2); //114 'piros alma'.indexOf('alma'); //6 'piros alma'.localeCompare('piros körte'); //-1 'piros alma'.replace('piros', 'sárga'); //"sárga alma" 'piros alma'.substr(2, 3); //"ros" 'piros alma'.split(' '); //["piros","alma"]
A Date objektumon keresztül különböző dátummal kapcsolatos funkcionalitás érhető el. Egy új időpontot létrehozni a new Date() utasítással lehet. Paraméter nélkül az aktuális időpontot adja vissza. Paraméterein keresztül adott időpont állítható be (new Date(év, hónap, nap[, óra, perc, másodperc, ezredmásodperc])). Műveletei három nagy csoportba tartoznak:
//Aktuális időpont lekérdezése var most = new Date(); most.toLocaleString(); //"2013. július 31. 15:54:58" most.getFullYear(); //2013 most.getMonth(); //6 most.getTime(); //1375279213631; 1970.01.01. óta eltelt ezredmásodpercek száma, a dátum belső ábrázolása most.setFullYear(2011); most.toLocaleString(); //"2011. július 31. 15:54:58" var maskor = new Date(2003, 7, 2); maskor.toLocaleString(); //"2003. augusztus 2. 0:00:00" most - maskor; //252345871891; ezredmásodpercben
A Math objektumon keresztül számos matematikai függvény érhető el. A teljesség igénye nélkül néhány példa:
A Math objektum jó néhány konstanst is rendelkezésünkre bocsát (csupa nagy betűsek):
Math.PI; //3.141592653589793 Math.sin(90 * Math.PI / 180); //1; 90 fok szinusza Math.random(); //pl. 0.47057095554085615 Math.random(); //pl. 0.5286946792885748 Math.round(1.6); //2 Math.floor(1.6); //1
A tömbök is speciális objektumok, megannyi hasznos metódussal. Néhány fontosabb tömbfüggvény:
var t = [1, 2, 3, 4, 5]; t.push(6); //6; tömb hosszát adja vissza; tömb: [1, 2, 3, 4, 5, 6] t.pop(); //6; tömb: [1, 2, 3, 4, 5] t.unshift(0); //6; tömb hosszát adja vissza; tömb: [0, 1, 2, 3, 4, 5] t.shift(); //0; tömb: [1, 2, 3, 4, 5] t.reverse(); //[5, 4, 3, 2, 1]; tömb: [5, 4, 3, 2, 1] t.splice(2, 1); //[3]; kivágott elemeket adja vissza; tömb: [5, 4, 2, 1] t.join('###'); //"5###4###2###1"
A reguláris kifejezések olyan minták, amelyek bizonyos karakterkombinációk szövegre illeszkedését vizsgálják. A minta az ábécé betűin túl speciális karaktereket használ az illeszkedés leírására.
Mielőtt a részletekbe belemennénk, nézzük meg a következő példát, amely azt vizsgálja, hogy egy adott szövegben szerepel-e valamilyen szám:
/\d+/.test('101 kiskutya'); //true /\d+/.test('A halál 50 órája'); //true /\d+/.test('Tíz kicsi néger'); //false
A sorok elején / jelek között található a minta, aminek illeszkedését a szövegre a test() metódussal nézzük meg. Mivel bármilyen számot nézni szeretnénk, így olyan szabályt kell megfogalmaznunk, ami tetszőleges számú számkaraktert néz egymás után. Egy tetszőleges számot a \d helyettesít, azt pedig a + jel írja le, hogy a számból 1 vagy több lehet egymás után.
Reguláris kifejezéseket leggyakrabban literálformával adunk meg. Ennek általános formája a következő:
/minta/módosítók
ahol a minta a normális karaktereken túl speciális karakterekből áll, a módosítók pedig az illesztés viselkedését szabályozó jelölők (karakterek). Az így létrehozott reguláris kifejezést vagy helyben felhasználjuk (ld. a fenti példát), vagy egy változóba eltároljuk:
var regkif = /\d+/; regkif.test('A halál 50 órája'); //true
Néha előfordul, hogy reguláris kifejezések létrehozását a RegExp objektumon (függvényen) keresztül hozzuk létre. Erre akkor van szükség, ha a mintát dinamikusan (pl. szövegösszefűzéssel vagy felhasználói inputból) szeretnénk létrehozni. Ekkor a RegExp() függvénynek szöveges paraméterként adjuk meg a mintát és a módosítókat:
new RegExp('minta', 'módosítók');
A fenti példa ezzel így néz ki:
var regkif = new RegExp('\\d+'); regkif.test('A halál 50 órája'); //true
A minta legegyszerűbb esetben az ábécé karaktereiből áll. Ekkor azt nézzük meg, hogy a megadott szöveg (minta), szerepel-e egy az egyben a szövegben:
/nap/.test('Immár a nap leáldozott'); //true
A reguláris kifejezések erőssége azonban a speciális karakterek használatakor jön elő. Ezekkel ugyanis általánosabb illeszkedési szabályokat tudunk meghatározni. A teljesség igénye nélkül néhány gyakrabban előforduló speciális karakter a következő:
Ha speciális karaktert szeretnénk normális karakterként használni, akkor \ jelet kell elérakni.
/^Teremtő/.test('Teremtőnk kérünk tégedet'); //true /velünk$/.test('Légy kegyes és maradj velünk'); //true /,\s*/.test('Őrizzed, óvjad népedet!'); //true; van-e benne vessző és utána opcionális szóköz //Van-e a szövegben ÉÉÉÉ. hónap NN. formátumban dátum? /\d{4}\.\s\w+\s\d{1,2}\./.test('Ma 2013. augusztus 2.-a van.'); //true /\d{4}\.\s\w+\s\d{1,2}\./.test('Kelt: Budapest, 2013.08.02.'); //false
Az illeszkedés módját a minta után megadott módosítók befolyásolják:
/^teremtő/.test('Teremtőnk kérünk tégedet'); //false /^teremtő/i.test('Teremtőnk kérünk tégedet'); //true
A reguláris kifejezéseknek mint objektumoknak két saját metódusa van:
/m.g/g.test('Isten, áldd meg a magyart!'); //true var t = /m.g/g.exec('Isten, áldd meg a magyart!'); t; //["meg"]; az illeszkedő szövegrész t.index; //12; az illeszkedés helye t.input; //"Isten, áldd meg a magyart!"; az eredeti szöveg
A reguláris kifejezéseket intenzíven használják a különböző szövegmetódusok:
//Példa a cserére és a globális módosító használatára "piros alma kukacos".replace(/os/, 'ított'); //"pirított alma kukacos" "piros alma kukacos".replace(/os/g, 'ított'); //"pirított alma kukacított" //Vezeték és keresztnév felcserélése var re = /(\w+)\s(\w+)/i; //vagy ha ékezeteket is támogatni szeretnénk var re = /([A-ZÍÉÁŰŐÚÖÜÓ]+)\s([A-ZÍÉÁŰŐÚÖÜÓ]+)/i; var sz = "Horváth Győző"; sz.replace(re, "$2, $1"); //Győző, Horváth //Vesszővel elválasztott szavak felbontása 'alma, körte,szilva, répa'.split(/,\s*/); //["alma", "körte", "szilva", "répa"]
Bővebben a reguláris kifejezésekről a JavaScript referenciában és leírásban lehet olvasni.
JavaScriptben lehetőség van egy függvény végrehajtását egy megadott idő eltelte utánra időzíteni. Alapvetően két függvény segítségével érhető ez el. A setTimeout() függvény egy függvény egyszeri végrehajtását végzi el a megadott idő elteltével. A setInterval() függvény viszont egy függvény adott időközönkénti ismételt végrehajtására szolgál. Mindkét függvénynél első paraméter a végrehajtandó függvény referenciája (vagy egy függvényliterál), második paraméterként pedig a késleltetés idejét kell megadni ezredmásodpercekben. Mindkét függvény egy időzítőazonosítóval tér vissza, amelyen keresztül meg lehet állítani az időzített végrehajtást. A setTimeout()-tal indított időzítőt a clearTimeout() függvénnyel lehet leállítani, a setInterval() párja a clearInterval(). Mindkét megállító függvénynek paraméterül az időzítőazonosítót kell megadni. Általános alakjuk tehát a következő:
//setTimeout és clearTimeout var időzítőAzonosító = setTimeout(függvény, ms); clearTimeout(időzítőAzonosító); //setInterval és clearInterval var időzítőAzonosító = setInterval(függvény, ms); clearInterval(időzítőAzonosító);
Néhány példa:
//Meghívandó függvény definiálása function csorog() { console.log('Brrrrrrr'); } //setTimeout példa: óracsörgés 2 másodperc múlva setTimeout(csorog, 2000); //vagy egyben setTimeout(function () { console.log('Brrrrrrr'); }, 2000); //Időzítő leállítása még a csörgés előtt var idozito = setTimeout(csorog, 2000); clearTimeout(idozito); //óra csörgése 2 másodpercenként setInterval(csorog, 2000); //ismételt csörgés leállítása var idozito2 = setInterval(csorog, 2000); clearInterval(idozito2);
Az időzítőket főleg hosszan tartó folyamatoknál használjuk. Azért van rájuk szükség, mert a JavaScript kód a böngészőben ugyanazon a programszálon fut, mint a felhasználói felület kezelése. Ez azt jelenti, hogy ha van egy 10 másodpercig futó kódunk (pl. prímszámkeresés), akkor a felület (azaz maga a böngésző) 10 másodpercig nem használható, nem reagál. Időzítők segítségével azonban lehetőség van ütemezni ezeknek a végrehajtását, időt adva a böngészőnek az egyéb felhasználói események végrehajtására is. Legelterjedtebb használata az animációknál volt (a CSS3 animációk megjelenése előtt).
Az időzítőkről bővebben például a Mozilla leírásában tudhatunk meg.
Egy alkalmazás írása közben számos hiba fordulhat elő. Egyrészt hibát jelezhet az értelmező a program futtatása során, másrészt mi magunk is jelezhetjük valamilyen módon, ha pl. egy függvény nem az előfeltételeknek megfelelő paramétereket kap, vagy nem a normális működést produkálja. Ebben az esetben megállapodhatunk, hogy speciális értékkel térünk vissza, pl. undefined-dal vagy null-lal, de a korszerűbb nyelvekben erre a megfelelő hiba dobása szolgál, a hívó programrészben pedig a keletkezett hibák elkapásával lehet a nem várt esetekre felkészülni. Ezt nevezzük kivételkezelésnek, amit a JavaScript nyelvi szinten is támogat.
A JavaScriptben számos előre beépített hibatípus létezik. Az általános Error objektumon kívül az alábbi hibaobjektumok fordulhatnak elő:
Mindegyik hibaobjektumnak két tulajdonsága van. A name a hiba típusát adja meg, míg a message a hiba leírását tartalmazza.
A kiváltott hibák kezelésére a try-catch-finally blokkok szolgálnak. A try blokkba kell írni a védendő kódot, amelyben a hiba előfordulását várjuk. Ha itt hiba keletkezik, akkor a blokk végrehajtása félbeszakad, és a vezérlés a catch blokknak adódik át, paraméterül megkapva a hibát reprezentáló kivételobjektumot. Itt kell kezelni a hibát. Végül finally blokkba írjuk azt a kódot, amit a try vagy catch blokkok után mindenféleképpen szeretnénk lefuttatni. A try blokkot vagy egy catch, vagy egy finally, vagy mindkettő követheti, így az alábbi lehetőségek fordulhatnak elő:
//Egy nem létező objektumra hivatkozva hiba lesz try { alma.kukacos = true; } catch (e) { console.log(e.name); console.log(e.message); } finally { //Akár el is hagyható console.log('Végem van...'); } //Az eredménye: //"ReferenceError" //"alma is not defined" //"Végem van...""
Kivételt akár mi is kiválthatunk a throw parancs segítségével. Dobhatunk egyrészt beépített hibaobjektumot az Error vagy valamelyik specifikusabb függvény segítségével, de hibaként generálhatunk saját hibaobjektumot is objektumliterál formájában, illetve objektumgenerátor vagy konstruktorfüggvény segítségével. Ebben az esetben a hibaobjektum bármilyen adattagokat tartalmazhat, de érdemes a natív hibaobjektumok alapján a name és a message mezőket megtartani.
//Beépített hiba dobása, pl. egy előfeltétel nem teljesülése esetén if (typeof a !== 'number') { throw new Error('Nem szam a parameter!'); } //Saját hiba dobása objektumliterál segítségével, pl. 0-val való osztásnál if (oszto == 0) { throw { name: 'DivisionByZeroError', message: 'Az oszto nulla!' }; } //Saját hiba dobása konstruktorfüggvénnyel function DivisionByZeroError(message) { this.name = "DivisionByZeroError"; this.message = message; } //... if (oszto == 0) { throw new DivisionByZeroError('Az oszto nulla!'); }
A kivételkezelés további részletei a dokumentációban olvashatók:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try catch
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
A tananyag az ELTE - PPKE informatika tananyagfejlesztési projekt (TÁMOP-4.1.2.A/1-11/1-2011-0052) keretében valósult meg.
A tananyag elkészítéséhez az ELTESCORM keretrendszert használtuk.