Visszatükrözés (reflection)
Általánosságban elmondható, hogy programozáskor, tudjuk előre, hogy a saját osztályainkból példányosított objektumoknak milyen interfésze
lesz. Elég kikeresni a forráskódban az objektum osztályát, és máris látjuk milyen publikus metódusokkal ill. tulajdonságokkal
fog rendelkezni az osztályból példányosított objektum. Saját függvényeink visszatérési értékének adattípusában is mindig biztosak lehetünk,
hiszen mi magunk írtuk a függvényt, azaz nem okozhat meglepetést.
A valóság azonban az, hogy gyakori az az eset, amikor a kód írásakor még nem tudjuk (tudhatjuk), hogy egy objektumnak lesz-e ilyen vagy
olyan nevű metódusa ill. tulajdonsága. Ennek az oka az, hogy az objektumot a böngésző szolgáltatja, és az objektum interfésze a
böngésző verziójától és típusától függően változhat. Számtalan példát lehetne bemutatni.
A JavaScript 1.6-os verziójában a nyelv beépített Array osztálya hét új metódussal bővült (indexOf, lastIndexOf, every, filter, forEach, map, some). Mielőtt használatba vennénk őket, meg kell győződnünk létezésükről, máskülönben futási hiba lépne fel.
Kivételkezelésnél például a catch blokkban attól függően, hogy milyen a dobott kivétel osztálya, különböző kódot futtathatunk le.
Mivel a JavaScript nem típusos nyelv, ezért a nyelv nem teszi lehetővé, hogy kikényszerítsük egy változó adattípusát. Ez hibák forrása lehet, ezért nem árt, ha mi magunk ellenőrizzük le például az osztálykonstruktornak átadott paraméterek adattípusát. Ha valamelyik paraméter adattípusa nem megfelelő, azonnal felhívhatjuk a programozó figyelmét a hibára. Így megkímélhetjük kollégáinkat a felesleges hibakereséstől, ami a nem megfelelő adattípus miatt jönne létre.
Visszatükrözésről (reflexió) akkor beszélünk, amikor futási idő alatt információkat szerzünk változókról és objektumokról a fent felsorolt és egyéb okokból.
A következőképpen tudjuk kideríteni, hogy egy bizonyos objektumnak létezik-e egy bizonyos tulajdonsága ill. metódusa:
if (valamiObjektum.valamiTul){
....
}
A fenti módszer azonban nem fog helyesen működni, ha a tesztelt tulajdonság értéke false, 0, vagy null („falsy” értékek). A typeof operátorral biztonságosan tudjuk megállapítani a tulajdonság, vagy metódus létezését.
if (typeof valamiObjektum.valamiTul == "undefined"){
....
}
A typeof operátor egy sztringet ad vissza, ami a kifejezés típusát jellemzi.
| adattípus | typeof visszatérési értéke |
|---|---|
| object | 'object' |
| function | 'function' |
| array | 'object' |
| number | 'number' |
| string | 'string' |
| boolean | 'boolean' |
| null | 'object' |
| undefined | 'undefined' |
Amint azt a fenti táblázatból is látjuk a typeof operátor nem jelzi az objektum osztályát, egyszerűen csak 'object' értékkel tér vissza.
Objektumok osztályát az instanceof operátorral tudjuk megállapítani.
var arr = [0,1,'2'];
if (arr instanceof Array){
alert("Ez egy tomb.");
}
if (arr instanceof Object){
alert("Ez egy objektum.");
}
A fenti példakód mindkét feltétele teljesül, mert JavaScriptben minden osztály
ősosztálya az Object osztály.
A fenti kód egy elegánsabb változata:
Object.prototype.isArray = function() {
return this.constructor == Array;
}
alert(arr.isArray()); // true
alert({}.isArray()); // false
Egy interfészes példa
osztálydiagramja
A JavaScript nem támogatja az interfészeket (interface), de azért valami hasonlóra képesek vagyunk az instanceof operátor felhasználásával.
Vegyük szemügyre a fenti UML osztálydiagramot. A most következő mintakód megpróbálkozik valami hasonlóval.
// két kisegítő metódussal bővítek minden objektumot
Object.prototype.implementsProp=function(propName){
return (this[propName]!=null);
};
Object.prototype.implementsFunc=function(funcName){
return this[funcName] && this[funcName] instanceof Function;
};
/**
* Egy osztály, amely tetszőleges Queue (sor) interfészt megvalósító
* objektumot vár a konstruktorában.
*/
function ClassVezerlo(){
// constructor
this._constructor = function(oArgs){
// jobb, ha mindjárt a konstruktorban leellenőrizem
// az átadott objektumot
if (!isQueue(oArgs[0])){
throw new Error("Invalid queue passed!");
}
// most már nyugodtan dolgozhatok az átadott objektummal,
// mert meggyőződtem róla, hogy az objektum egy „sor”.
};
// privát metódus, ami megállapítja, hogy az átadott objektum
// implementálta-e a Queue interfészt.
var isQueue = function(obj){
return (obj.implementsFunc('put') &&
obj.implementsFunc('get' ) &&
obj.implementsFunc('isEmpty'));
};
this._constructor.apply(this,arguments);
}
new ClassVezerlo({});
A félreértés elkerülése érdekében hangsúlyoznom kell, hogy a fenti kód nem azt sugallja, hogy használjunk JavaScriptben is interfészeket. A mintakód csupán azt bizonyítja, mennyire rugalmas tud lenni ez a nyelv.
Egy tetszőleges objektum felépítését a for-in ciklus segítségével tudjuk felderíteni. A ciklusmag annyiszor fog lefutni, amennyi az objektum elemeinek a száma.
for (tulajdonsag in objektum) {
// alert(objektum[tulajdonsag]);
// alert(tulajdonsag);
}
Az öröklött tulajdonságokat és metódusokat viszont nem írja ki. Ellenben kiírja a prototype tulajdonsághoz adott tulajdonságokat.
var o = {};
function Class1(name){
this.name = name;
}
Class1.prototype.toString = function(){
alert('I am a Class1 instance');
}
var o2 = new Class1('jancsi');
// nem ír ki semmit, az o objektum a toString metódust örökölte
for (tulajdonsag in o){
alert(tulajdonsag + " : " + o[tulajdonsag]);
}
// amit kiír: name, toString
for (tulajdonsag in o2){
alert(tulajdonsag + " : " + o2[tulajdonsag]);
}
Az objektumok hasOwnProperty() metódusával letesztelhetjük, hogy a megadott tulajdonságnév az adott objektumhoz tartozik-e. Az előző példakódot átírva, most már a toString() metódus sem kerül kiírásra.
function Class1(name){
this.name = name;
}
Class1.prototype.toString = function(){
alert('I am a Class1 instance');
}
// amit kiír: name
for (tulajdonsag in o2){
if (o2.hasOwnProperty("toString")){
alert(tulajdonsag + " : " + o2[tulajdonsag]);
}
}
A for-in ciklusra további példakódot a /reflexio/ könyvtárban talál.