Vissza az előzőleg látogatott oldalra (nem elérhető funkció)Vissza a tananyag kezdőlapjára (P)Ugrás a tananyag előző oldalára (E)Ugrás a tananyag következő oldalára (V)Fogalom megjelenítés (nem elérhető funkció)Fogalmak listája (nem elérhető funkció)Oldal nyomtatása (nem elérhető funkció)Oldaltérkép megtekintése (D)Keresés az oldalon (nem elérhető funkció)Súgó megtekintése (S)

Modern programozási minták a kliens és szerveroldali webprogramozásban / Objektum-orientált minták JavaScriptben

Tanulási útmutató

Összefoglalás

A JavaScript nyelv szó szerint objektum-orientált nyelv, osztályok nem lévén benne. Ez a fejezet azt mutatja be, hogy a JavaScript nyelv dinamikus objektumaival és prototípusos mivoltával hogyan valósítható meg a kódújrahasznosítás, valamint hogyan lehet klasszikus objektum-orientált mintákat megvalósítani.

Objektum-orientált minták JavaScriptben

Prototípusosság mint a kód-újrahasznosítás eszköze

A prototípus egy olyan objektum, amely alapján egy másikat modellezni szeretnénk. Kicsit hasonlít az osztályokhoz abban a tekintetben, hogy hasonló objektumokat hozhatunk vele létre, de különbözik abban, hogy a prototípus maga is objektum.

A JavaScript prototípusos nyelv. Ez azt jelenti, hogy lehetőség van egy objektumot egy másik mintájára elkészíteni. Erre a nyelv többféle lehetőséget ad. Egyrészt kihasználhatjuk az objektumok dinamikus jellegét, és egy objektumban már meglévő adatot és/vagy funkcionalitást átmásolhatunk egy másik objektumba. Ilyetén a kiindulási objektum mintául szolgál az új objektum létrehozásához.

A másik lehetőség a prototípus-objektumok adta működés kihasználásában rejtőzik. Ekkor a prototípusosság koncepciója úgy valósul meg a nyelvben, hogy a készítendő objektum tartalmaz egy hivatkozást a modellül szolgáló objektumra. Ekkor ugyanis ha az adott tulajdonság az adott objektumban nincsen meg, akkor a prototípus objektum szolgáltathatja azt. Az adott objektum interfészében tehát megjelennek a prototípus-objektumok adatai és metódusai is, vagy másképpen, az adott objektumon keresztül elérhetőek a prototípus-objektumok el nem rejtett tulajdonságai is.

Ilyen formán a prototípusosság a kód-újrahasznosítás eszközévé is válik, hiszen egy már megírt funkcionalitás újraírás nélkül megjelenik egy másik objektumban. A következő részekben az újrahasznosításnak a fent említett két módszerét nézzük meg, először az objektumok dinamikusságát, majd a prototípusláncból fakadó előnyöket kihasználva.

Vissza a tartalomjegyzékhez

Kód-újrahasznosítás (prototípusosság) dinamikus objektumokkal

Az objektumok dinamikusságát kihasználó kód-újrahasznosítás sematikus ábrájaAz objektumok dinamikusságát kihasználó kód-újrahasznosítás sematikus ábrája

Az alábbiakban olyan technikákat nézünk meg, amelyek segítenek egy már megírt kódrészletet újrahasznosítani. JavaScriptben a kód-újrahasznosítás szorosan összefügg a nyelv prototípusos jellegével, ahol prototípus alatt modellobjektumot értünk. Az újrahasznosítás során ugyanis nem kell újra megírni a kódot, hanem a modell mint prototípus mintájára kell alkalmazni azt. Ebben a fejezetben a JavaScript objektumok dinamikus jellegét használjuk ehhez.

Tulajdonságok másolása – a Mixin minta

Fentebb láthattuk, hogy a JavaScript objektumok futás időben tetszőleges tulajdonságokkal bővíthetők. Ezt kihasználva megtehetjük azt, hogy az újrahasznosítandó kódrészletet egyszerűen bemásoljuk a célobjektumba. Ezt szokták bővítésnek vagy mixinnek nevezni, jelezve, hogy meglévő tulajdonságokat vegyítenek egy másik objektumba.

Forráskód
//Kiindulási objektumok
var o1 = {
    a: 1,
    b: 2
};
var o2 = {
    b: 42,
    c: 3
};
//Mixin
o2.a = o1.a;
o2.b = o1.b;
//Teszt
ok( o2.a === 1, 'Az a tulajdonság átmásolódott');
ok( o2.b === 2, 'A b tulajdonságot felülírta a másolandó objektum b tulajdonsága');
ok( o2.c === 3, 'A c tulajdonság érintetlen maradt.');

A fenti példában látható, hogy tulajdonság nevének egyezésekor a másolt objektum felülírja a célobjektumbeli tulajdonság értékét.

Az extendShallow() segédfüggvény

Annak érdekében, hogy ne tulajdonságról tulajdonságra kelljen a másolást megtenni, érdemes egy segédfüggvényt bevezetni, mely automatizálja ezt a feladatot. A legtöbb keretrendszer eltérő néven ugyan, de tartalmaz ilyen függvényt. Szokták extend(), augment(), mixin(), copy() vagy shallowCopy() néven is illetni. Az alábbiakban extendShallow() néven hivatkozunk rá, jelezvén a másolás tulajdonságát.

Forráskód
var extendShallow = function extendShallow(objTo, objFrom) {
    for (var prop in objFrom) {
        if (objFrom.hasOwnProperty(prop)) {
            objTo[prop] = objFrom[prop];
        }
    }
    return objTo;
};

Néha a cikluson belüli elágazást elhagyják, és így a prototípusláncon keresztül elérhető összes tulajdonság átmásolásra kerül. A fenti extendShallow() függvény csak egy objektumot másol a célobjektumba. Az alábbi változtatással tetszőleges számú objektumot megadhatunk, amelyek egymás után érvényesülnek a célobjektumon (ld. underscore.js).

Forráskód
var extendShallow = function extendShallow (obj) {
    [].slice.call(arguments, 1).forEach(function(source) {
        for (var prop in source) {
            if (source.hasOwnProperty(prop)) {
                obj[prop] = source[prop];
            }
        }
    });
    return obj;
};

Használata a következő:

Forráskód
//Kiindulási objektumok
var o1 = {
    a: 1,
    b: 2
};
var o2 = {
    b: 42,
    c: 3
};
extendShallow(o2, o1);
//Tesztnek ld. az előző teszteket
Kiindulási és végállapotok a bővítés soránKiindulási és végállapotok a bővítés során

A másolás során látható, hogy az o2 objektum megváltozik. Ha úgy szeretnénk egy objektumot előállítani, hogy az az o2 és o1 objektum vegyítése legyen, akkor egy üres objektumot kell kibővítenünk.

Forráskód
var o3 = extendShallow(, o2, o1);
//Teszt
ok( o3.a === 1, 'Az a tulajdonság átmásolódott');
ok( o3.b === 2, 'A b tulajdonság átmásolódott');
ok( o3.c === 3, 'A c tulajdonság átmásolódott');

Gyakran nincsen szükségünk az összes tulajdonság átmásolására, hanem csak néhány kijelölt tulajdonságot szeretnénk a fogadóobjektumba injektálni. Ekkor az extendShallow() segédfüggvény csak két objektumot tud paraméterként fogadni, a másolandó tulajdonságok szöveges nevei a harmadik paramétertől kezdve vannak felsorolva.

Forráskód
//Meghatározott tulajdonságok másolása
var extendShallow = function extendShallow(objTo, objFrom) {
    for (var i = 2; i < arguments.length; i++) {
        var propName = arguments[i];
        objTo[propName] = objFrom[propName];
    }
    return objTo;
};
//Használata a fenti o1 és o2 objektumokon
extendShallow(o2, o1, 'a');
ok( o2.a === 1, 'Az a tulajdonság átmásolásra került');
ok( o2.b === 42, 'A b tulajdonság nem másolódott át');

A kétféle megközelítést, a több objektum vegyítését és a szelektív másolást, ötvözhetjük egyetlen segédfüggvényben, aminek a paraméterezése dönti el, melyik módot használjuk.

Forráskód
var extendShallow = function extendShallow(objTo, objFrom) {
    if (arguments[2] && typeof arguments[2] === 'string') {
        for (var i = 2; i < arguments.length; i++) {
            var propName = arguments[i];
            objTo[propName] = objFrom[propName];
        }
    } else {
        [].slice.call(arguments, 1).forEach(function(source) {
            for (var prop in source) {
                if (source.hasOwnProperty(prop)) {
                    objTo[prop] = source[prop];
                }
            }
        });
    }
    return objTo;
}

Referenciatípusok másolása

A fenti példák olyan szempontból egyszerűek voltak, hogy elemi értékek kerültek átmásolásra. Mi a helyzet azonban az összetett típusokkal, úgymint az objektumokkal, tömbökkel és függvényekkel? Az alábbi példában az oFrom objektum szolgál mintául az o1 és o2 objektumoknak.

Forráskód
//A kiindulási objektumok
var oFrom = {
    arr: [],
    obj: {
        a: 1
    },
    put: function (elem) {
        this.arr.push(elem);
    }
};
//Célobjektumok
var o1 = extendShallow(, oFrom);
var o2 = extendShallow(, oFrom);
//Manipulálás
o1.put(42);
o1.obj.a = 100;
//Teszt
ok( o1.put === o2.put, 'Mindkét objektum ugyanazt a függvényt éri el');
ok( o1.arr === o2.arr, 'A tömb is ugyanaz');
ok( o1.obj === o2.obj, 'A belső objektum is ugyanaz');
ok( o2.arr[0] === 42, 'Ugyanazt a tömböt érik el');
ok( o2.obj.a === 100, 'Ugyanazt az objektumot érik el');
//Teljes objektumcsere
o1.obj = {
    b: 2
};
ok( o2.obj.a === 100, 'Az o2 obj tulajdonsága nem változik');
ok( o1.obj.a === undefined, 'Az o1-ben nincs obj.a');

Az extendShallow() függvény másolatot készít a forrásobjektum tulajdonságaiból a célobjektumba. Ez elemi objektumoknál egy új érték létrejöttét jelenti, hiszen ezek érték szerint másolódnak, összetett objektumoknál azonban a másolás csak referenciaszinten valósul meg, a belső objektum tulajdonságai közösek. Ha viszont az egész objektumot lecseréljük, akkor megszűnik az egyezés.

Az extendShallow függvény az összetett típusokat referencia szerint másolja átAz extendShallow függvény az összetett típusokat referencia szerint másolja át

Tömbök és objektumok másolása – az extendDeep() segédfüggvény

Sok esetben probléma, ha a tömbökről és objektumokról nem teljes másolat készül, hanem közösek. Ekkor érdemes az extendDeep() segédfüggvényt használni, amely a tömbök és objektumok esetén is teljes másolatot készít rekurzívan bejárva ezeket a struktúrákat. Ez a függvény is képes több modellobjektumot felhasználni a bővítésre, valamint két objektum között csak bizonyos tulajdonságokat átmásolni. Az extendDeep() lelke a copy függvény, mely tömbök és objektumok esetén új példányt hoz létre, és végzi el a rekurzív hívást.

Forráskód
var extendDeep = function extendDeep(objTo, objFrom) {
    var copy = function copy(objTo, objFrom, prop) {
        if (typeof objFrom[prop] === "object") {
            objTo[prop] = (Object.prototype.toString.call(objFrom[prop]) === '[object Array]') ? [] : ;
            extendDeep(objTo[prop], objFrom[prop]);
        } else {
            objTo[prop] = objFrom[prop];
        }
    }
    if (arguments[2] && typeof arguments[2] === 'string') {
        for (var i = 2; i < arguments.length; i++) {
            var prop = arguments[i];
            copy(objTo, objFrom, prop);
        }
    } else {
        [].slice.call(arguments, 1).forEach(function(source) {
            for (var prop in source) {
                if (source.hasOwnProperty(prop)) {
                    copy(objTo, source, prop);
                }
            }
        });
    }
    return objTo;
};
//Használata a legutóbbi oFrom, o1 és o2 objektumokon
var o1 = extendDeep(, oFrom);
var o2 = extendDeep(, oFrom);
//Manipulálás
o1.put(42);
o1.obj.a = 100;
//Teszt
ok( o1.put === o2.put, 'Mindkét objektum ugyanazt a függvényt éri el');
ok( o1.arr !== o2.arr, 'A tömbök különböznek');
ok( o1.obj !== o2.obj, 'A belső objektumok különböznek');
ok( o2.arr[0] === undefined, 'Más tömbbel dolgoznak');
ok( o2.obj.a === 1, 'Más objektummal dolgoznak');

Az extendDeep() a függvényekről nem készít külön másolatokat, azok továbbra is a modellobjektumbeli metódusokra hivatkoznak.

Az extendDeep segédfüggvény működéseAz extendDeep segédfüggvény működése

Metódusok kötése – az extendBind() segédfüggvény

A függvények referencia szerinti másolásával a függvényen belüli this mindig arra az objektumra mutat, amelyen meghívták. Mi van azonban akkor, ha azt szeretnénk, hogy a bővítés után is az eredeti objektumbeli adatokkal dolgozzon a másolt függvény? Ez akkor fordulhat elő, ha olyan funkcionalitást másolunk, amelynek működési nyilvántartása a modellobjektumban történik. Az extendBind() segédfüggvény oly módon másolja át egy modellobjektum összes metódusát, hogy a this objektum kontextusa meghatározható. Ennek hiányában az objFrom objektumhoz lesznek a metódusok kötve.

Forráskód
var extendBind = function extendBind(objTo, objFrom, context) {
    context = context || objFrom;
    for (var prop in objFrom) {
        if (objFrom.hasOwnProperty(prop) && typeof objFrom[prop] === 'function') {
            objTo[prop] = objFrom[prop].bind(context);
        }
    }
    return objTo;
}
//Használata
var o1 = {
    name: 'o1'
};
var o2 = {
    name: 'o2',
    a: function() {
        return this.name;
    }
};
var o3 = {
    name: 'o3'
};
extendBind(o1, o2, o3);
ok( o1.a() === 'o3', 'bind sikerült');

Tulajdonságok klónozása – funkcionális bővítés és az extendFunc() segédfüggvény

Az előző megoldásokban a másolandó tulajdonságokat objektumok tartalmazták. Tulajdonságokat bemásolni azonban másképp is lehet. Ha a tulajdonságokat egy függvényben a this objektumhoz kapcsoljuk, majd a függvénynek a célobjektumot mint this-t adjuk át, akkor a célobjektumban létrejönnek a tulajdonságok. Ezek viszont most már nem másolatok, hanem valódi klónok lesznek. Mivel a bemásolás ebben az esetben függvény segítségével valósult meg, ezért ezt a változatot funkcionális bővítésnek nevezzük.

Forráskód
var funcFrom = function funcFrom() {
    this.arr = [];
    this.obj = {
        a: 1
    };
    this.put = function (elem) {
        this.arr.push(elem);
    };
    return this;
};
var o1 = funcFrom.call();
var o2 = funcFrom.call();
//Teszt
ok( o1.put !== o2.put, 'Két különböző put függvény');
ok( o1.arr !== o2.arr, 'Különböző belső tömbök');
ok( o1.obj !== o2.obj, 'Különböző belső objektumok');
A funkcionális bővítés példájaA funkcionális bővítés példája

A fenti folyamatot is általánosíthatjuk egy segédfüggvényben. A másolandó funkcionalitást tehát függvényekbe helyezzük, és a this objektumot bővítjük ki vele. A extendFunc() függvény pedig végigveszi a második paramétertől kezdve a függvényeket és sorban mindegyikkel bővíti az első paraméterben érkező objektumot. A lenti példában a this objektum bővítését az extendShallow() függvénnyel végezzük el.

Forráskód
//A clone segédfüggvény
var extendFunc = function extendFunc(obj) {
    [].slice.call(arguments, 1).forEach(function(source) {
        if (typeof source === 'function') {
            source.call(obj);
        }
    });
    return obj;
}
//Másolandó funkcionalitások
var func1 = function func1() {
    extendShallow(this, {
        random: function (n) {
            return Math.floor(Math.random() * n);
        }
    });
};
var func2 = function func2() {
    extendShallow(this, {
        add: function (a, b) {
            return a + b;
        }
    });
};
//Célobjektumok létrehozása
var o1 = extendFunc(, func1, func2);
var o2 = extendFunc(, func1, func2);
//Teszt
ok( typeof o1.random === 'function', 'A random függvény elérhető');
ok( typeof o2.add === 'function', 'Az add függvény elérhető');
ok( o1.random !== o2.random, 'A klónozott függvények különböznek');

Összefoglalás

Az objektumok dinamikus jellegét kihasználva lehetőség van egy objektum – mint prototípus – tulajdonságait egy másik objektumba helyezni. Ezt többféleképpen megtehetjük.

Vissza a tartalomjegyzékhez

Kód-újrahasznosítás (prototípusosság) prototípus-objektummal

A prototípus-objektummal történő kód-újrahasznosítás sematikus ábrájaA prototípus-objektummal történő kód-újrahasznosítás sematikus ábrája

Ebben a fejezetben a kód újrahasznosítását a prototípuslánc segítségével oldjuk meg. Az alapötlet az, hogy az újrahasznosítandó kódot tartalmazó modellobjektumot most nem bemásoljuk a célobjektumba, hanem azt prototípus-objektumként vesszük fel, így annak tulajdonságait a célobjektum a prototípusláncon keresztül elérheti. A célobjektum pedig egyedi sajátosságait saját tulajdonságain keresztül érvényesítheti, elrejtve a prototípus tulajdonságait.

Forráskód
//Prototípus
var oFrom = {
    a: 1,
    obj: {
        l: true
    },
    hello: function () {
        return 'hello';
    }
};
//Célobjektumok
var o1 = Object.create(oFrom);
var o2 = Object.create(oFrom);
ok( o1.a === 1, 'Prototípus adattagja elérhető');
ok( o1.hello() === 'hello', 'Prototípus metódusa elérhető');
//Írás a célobjektumokba
o1.b = 2;
o2.a = 11;
ok( o1.b === 2, 'Saját adattag elérhető');
ok( o2.a === 11, 'Saját adattag eltakarja a prototípus azonos nevű adattagját');
ok( o1.hello === o2.hello, 'A prototípusban lévő látható tulajdonságok közösek');
//Összetett adatszerkezetek
o1.obj.l = false;
ok( o1.obj === o2.obj, 'A prototípusban lévő összetett adatszerkezetek közösek');
ok( o2.obj.l === false, 'A prototípusban lévő összetett adatszerkezetek elemein bekövetkező változások közösek');
o2.obj = {
    l: true
};
ok( o1.obj !== o2.obj, 'A teljes objektumot felüldefiniálva elrejthető a prototípus objektuma');
ok( o2.obj.l !== o1.obj.l, 'Így már külön állapot látszik o2-ben');
Prototípusos kód-újrahasznosítás a példa alapjánPrototípusos kód-újrahasznosítás a példa alapján

A fenti példában látható, hogy a prototípus-objektumban lévő összetett adatszerkezetek (objektumok, tömbök) elemeinek változtatása a prototípus-objektumban érvényesül, így minden célobjektumban tükröződik. Ha úgy szeretnénk egy összetett adatszerkezet elemeit megváltoztatni, hogy az ne a prototípus-objektumban íródjon felül, akkor az összetett adatszerkezetet a célobjektumban kell felvenni.

Ezt a kis részt összefoglalva megállapítható, hogy a JavaScript nyelv prototípusossága egyszerűen adódik a prototípus-objektumok által. A prototípus-objektumok tárolják a minden rá hivatkozó objektum számára közös metódusokat és adattagokat. Általánosságban elmondható, hogy metódusok esetén ez hasznos dolog, az állapotot képviselő adattagok esetén azonban veszélyeket rejthet magában. Arról, hogy a különböző kód-újrahasznosítási mintákat hogyan lehet jól felhasználni, a következő fejezetek írják le.

Vissza a tartalomjegyzékhez

Objektum-létrehozási minták

Az előzőekben áttekintettük, hogy milyen tulajdonságai vannak a JavaScript objektumoknak, és milyen lehetőségek állnak rendelkezésre egy meglévő kód újrahasznosítására. Az alábbiakban ezeket felhasználva nézzük meg azt, hogy objektumok létrehozását hogyan is lehet elvégezni.

Egy objektum létrehozása

JavaScriptben egy objektum létrehozása legegyszerűbben az objektumliterállal vagy az Object.create() metódussal tehető meg.

Forráskód
var c = {
    name: 'Sári',
    dateOfBirth: {
        year: 2004,
        month: 11,
        day: 14
    },
    getName: function getName() {
        return this.name;
    },
    setName: function setName(name) {
        this.name = name;
    }
};

Több objektum létrehozása – Gyár (Factory) minta

Ha több ugyanolyan funkcionalitású objektumot szeretnénk létrehozni, akkor egy olyan függvényt kell készítenünk, amely a megfelelő objektumot adja vissza. Ez nem más, mint a Gyár (Factory) minta.

Forráskód
var child = function child() {
    return {
        name: 'Anonymous',
        dateOfBirth: {
            year: 1970,
            month: 1,
            day: 1
        },
        getName: function getName() {
            return this.name;
        },
        setName: function setName(name) {
            this.name = name;
        }
    };
};
var c1 = child();
var c2 = child();
ok( c1 !== c2, 'Különböző objektumok jönnek létre');
ok( c1.dateOfBirth !== c2.dateOfBirth, 'A belső objektumok is különböznek');
ok( c1.getName !== c2.getName, 'A metódusok is különböznek');

A child() függvényt semmi nem különbözteti meg a többi függvénytől. Azért, hogy jobban látszódjék az objektum létrehozásának szándéka, tegyük egyértelműbbé új objektum konstruálásának interfészét.

Forráskód
var child = {
    create: function create() {
        return {
            name:        /*...*/,
            dateOfBirth: /*...*/,
            getName:     /*...*/,
            setName:     /*...*/
        };
    }
};
var c1 = child.create();
var c2 = child.create();

Paraméteres gyárfüggvények

Kényelmes dolog létrehozáskor megadni az újonnan létrejövő objektum tulajdonságainak értékét. Ehhez egy inicializáló objektumot adunk át a gyárfüggvénynek, amivel felülírjuk a modellobjektumból származó tulajdonságok értékét az extendDeep() függvénnyel.

Forráskód
var child = {
    create: function create(props) {
        return extendDeep({
            name:        /*...*/,
            dateOfBirth: /*...*/,
            getName:     /*...*/,
            setName:     /*...*/
        }, props || );
    }
};
var c1 = child.create();
var c2 = child.create({
    name: 'Zsófi',
    dateOfBirth: {
        year: 2006,
        month: 9,
        day: 1
    }
});
ok( c1.name === 'Anonymous', 'Inicialzáló objektum hiányában a modellobjektum értékei vannak');
ok( c2.name === 'Zsófi', 'Az inicializáló objektum értékei felülírják a modellobjektum értékeit');

Privát adattagok és metódusok

JavaScriptben az objektumok adattagjai és metódusai mind publikusak, a nyelvben nincsen speciális szintaxis privát, védett vagy publikus tulajdonságok létrehozására. Closure segítségével azonban létrehozhatók privát adattagok és metódusok. Ekkor a gyárfüggvényen belül kell ezeket a privát tulajdonságokat definiálni, amik kívülről közvetlenül nem érhetők el, viszont megmaradnak a gyárfüggvény closure-jében, és a publikus metódusokon keresztül meghívhatók. Azokat a publikus függvényeket, amelyek privát adattagokat vagy metódusokat használnak, privilegizált metódusoknak hívjuk.

Forráskód
var child = {
    create: function create(props) {
        //Privát adattag
        var secretNickName = '';
        return extendDeep({
            //Publikus adattagok és metódusok
            name: 'Anonymous',
            dateOfBirth: {
                year: 1970,
                month: 1,
                day: 1
            },
            getName: function getName() {
                return this.name ;
            },
            setName: function setName(name) {
                this.name = name;
            },
            //Privilegizált metódusok
            setSecretNickName: function (name) {
                secretNickName = name;
            },
            getSecretNickName: function () {
                return secretNickName;
            }
        }, props || );
    }
};
var c1 = child.create();
c1.setSecretNickName('Fairy');
ok( c1.secretNickName === undefined, 'Privát adattag kívülről nem érhető el');
ok( c1.getSecretNickName() === 'Fairy', 'Privilegizált metódusok hozzáférnek');

Metódusok hatékony tárolása

Az eddigi megoldásoknak az a hátránya, hogy minden adattag és metódus annyi példányban jött létre, ahány objektumot létrehoztunk. Ez az adattagoknál nem is jelent gondot, hiszen az egyes objektumoknak saját állapotterük van, a metódusoknál azonban felesleges, elég belőlük egy példány, amely az adott objektum állapotterével dolgozik. A hatékony tárolás a következő:

Az elvárt interfész és a tárolás sematikus módjaAz elvárt interfész és a tárolás sematikus módja

Metódusok hatékony tárolása bővítéssel

Ezt az elvárást többféleképpen is teljesíthetjük. Ha van egy modellobjektumunk, ami tartalmazza az adattagokat és a metódusokat is, akkor ebből mély bővítéssel olyan objektumokat hozhatunk létre, amelyekbe az adattagok átmásolódnak, de a metódusok a modellobjektumbeli metódusokra mutatnak. Ennek egyetlen hátránya, hogy az egyes példányobjektumokban a metódusok mint referenciák megjelennek.

Forráskód
var child = (function child() {    
    var childProto = {
        name:        /*...*/,
        dateOfBirth: /*...*/,
        getName:     /*...*/,
        setName:     /*...*/
    };
    return {
        create: function create(props) {
            return extendDeep(, childProto, props);
        }
    }
})();
var c1 = child.create();
var c2 = child.create();
ok( c1.getName === c2.getName, 'A metódusok közösek');
ok( c1.dateOfBirth !== c2.dateOfBirth, 'Adattagok nem közösek');
Metódusok hatékony tárolása bővítésselMetódusok hatékony tárolása bővítéssel

Metódusok hatékony tárolása bővítéssel 2.

Az alábbi megoldásnak az lehet az előnye, hogy szétválasztja a metódusokat, vagy általában a közös részeket, és az adattagokat. A metódusokat egyszintű bővítéssel, az adattagokat mély bővítéssel másolja. Ez akkor jó, ha a közös részben pl. objektumok vagy tömbök vannak, amelyek így minden objektum számára közösek.

Forráskód
var child = (function child() {    
    var methodsProto = {
        getName:     /*...*/,
        setName:     /*...*/
    };
    var dataProto = {
        name:        /*...*/,
        dateOfBirth: /*...*/
    };
    return {
        create: function create(props) {
            var obj = extendShallow(, methodsProto);
            return extendDeep(obj, dataProto, props);
        },
        methodsProto: methodsProto,
        dataProto: dataProto
    }
})();
var c1 = child.create();
var c2 = child.create();
ok( c1.getName === c2.getName, 'A metódusok közösek');
ok( c1.dateOfBirth !== c2.dateOfBirth, 'Adattagok nem közösek');

Metódusok hatékony tárolása prototípuslánccal

A másik lehetőség, hogy a metódusokat egy objektumba helyezzük, majd ezt adjuk meg a létrejövő objektumok prototípusának. A létrejövő objektumokba pedig csupán az adattagokat másoljuk. A metódusok objektumát is elérhetővé tesszük a methods tulajdonságon későbbi felhasználás céljából (ld. öröklés lejjebb).

Forráskód
var child = (function() {
    var methods = {
        getName: /*...*/,
        setName: /*...*/
    };
    var data = {
        name:        /*...*/,
        dateOfBirth: /*...*/
    };
    return {
        create: function(props) {
            return extendDeep(
                Object.create(methods),
                data,
                props || 
            );
        },
        methods: methods
    };
})();
Metódusok hatékony tárolása prototípuslánccalMetódusok hatékony tárolása prototípuslánccal

Metódusok hatékony tárolása és a privát adattagok

Sajnos JavaScriptben nem lehet megtenni azt, hogy a privilegizált metódusokat közös helyre tegyük. Ennek az egyszerű oka az, hogy privát adattagok csak closure-ben létezhetnek. A closure viszont függvényhez tartozik. Egy függvény, egy closure. Ha tehát objektumonként szeretnénk privát adatokat tárolni, akkor annyi closure-t és ennek megfelelően annyi függvényt is kell létrehozni. És csak ezen függvényen belüli függvények érik el a privát adattagokat. A closure-t kívülről elérni vagy paraméterként átadni nem lehet.

A privát adattagokat és privilegizált metódusokat tehát objektumonként kell felvenni továbbra is a gyárfüggvényen belül.

Forráskód
var child = (function() {
    var publicMethods = {
        getName: /*...*/,
        setName: /*...*/
    };
    var publicData = {
        name:        /*...*/,
        dateOfBirth: /*...*/
    };
    return {
        create: function(props) {
            //Privát adattag
            var secretNickName = /*...*/;
            //Privilegizált metódusok
            var privilegedMethods = {
                setSecretNickName: /*...*/,
                getSecretNickName: /*...*/
            };
            return extendDeep(
                Object.create(publicMethods),
                privilegedMethods
                publicData,
                props || 
            );
        },
        methods: publicMethods
    };
})();

Vissza a tartalomjegyzékhez

Öröklési minták

A klasszikus OOP-ben a kód újrafelhasználásának szinte egyetlen eszköze az osztályok közötti öröklés. Ezzel érik el azt, hogy a gyerekosztályok használhatják a szülőosztályok megfelelően beállított adattagjait és metódusait.

JavaScriptbeli megvalósításkor induljunk megint ki az elvárásokból. Fentebb már láthattuk, hogy célszerű szétválasztani egy adott viselkedés állapotterét és metódusait. Öröklés esetén két szereplő van: az egyik az, akinek az állapotterét és metódusait szeretnénk felhasználni, az ős; a másik az, aki felhasználja és kiegészíti ezeket a saját állapotterével és metódusaival. A kiegészítést úgy kell megtennie, hogy még véletlenül se írja felül a szülőobjektum állapotterét és metódusát, hiszen más objektumok ezt használják vagy ez alapján jönnek létre.

Az örökléses tárolás sematikus kódjaAz örökléses tárolás sematikus kódja

Öröklés prototípuslánccal

A metódusok öröklése elérhető a megfelelő prototípuslánc felépítésével. A származtatott modell metódusai a szülő metódusmodelljeit állítja be prototípusnak. A példányobjektumok pedig a gyerek metódusmodelljeit állítják be prototípusuknak.

Öröklés prototípuslánccalÖröklés prototípuslánccal

Az alábbi példában a fenti ábra megvalósításán túl a gyerekmetódusok objektuma egy referenciát is tartalmaz a szülőmetódusokra (_super), hogy azok akkor is elérhetőek legyenek, ha a gyerek elrejti azokat a szülő elől a prototípusláncban (ld. pl. getName()).

Forráskód
var preschool = (function(_super) {
    var methods = {
        getSign: function getSign() {
            return this.sign;
        },
        setSign: function setSign(sign) {
            this.sign = sign;
        },
        getName: function getName() {
            var name = this._super.getName.call(this);
            return name + ' (preschool)';
        }    
    };
    var publicMethods = extendShallow(
        Object.create(_super.methods), 
        methods,
        {
            _super: _super.methods
        }
    );
    var publicData = {
        sign: 'default sign'
    };
    return {
        create: function(props) {
            return extendDeep(
                Object.create(publicMethods),
                _super.create(),
                publicData,
                props || 
            );
        },
        methods: publicMethods
    };
})(child);
var p1 = preschool.create();
var p2 = preschool.create();
p1.setName('Dávid');
ok( p1.name !== undefined, 'Ős tulajdonságai elérhetők');
ok( p1.sign !== undefined, 'Saját tulajdonságok is megvannak');
ok( p1.dateOfBirth !== p2.dateOfBirth, 'Objektumonkénti állapot');
ok( p1.setName === p2.setName, 'Ős metódusai közösek');
ok( p1.setSign === p2.setSign, 'Saját metódusok közösek');
ok( p1.getName() === 'Dávid (preschool)', 'Ősmetódus felülírva és meghívva');

Öröklés bővítéssel – kompozíció

Több modellobjektum metódusainak és adattagjainak használata elérhető úgy is, hogy a modellobjektumok tulajdonságaival egyszerűen kibővítjük a célobjektumot. Így a célobjektumban a bővítés sorrendjétől függően megjelennek az adattagok mély másolással, a metódusok pedig a modellobjektumok metódusaira hivatkoznak. Ezzel a módszerrel jól szimulálható a többszörös öröklés.

Öröklés bővítésselÖröklés bővítéssel
Forráskód
var childProto = {
    name:        /*...*/,
    dateOfBirth: /*...*/,
    getName:     /*...*/,
    setName:     /*...*/
};
var preschoolProto = {
    sign:    /*...*/,
    getSign: /*...*/,
    setSign: /*...*/
};
var preschool = {
    create: function(props) {
        return extendDeep(, childProto, preschoolProto, props);
    }
};
//Teszteket ld. az előző fejezetben
//Kivéve, hogy most nem definiáltuk felül a getName() metódust

Ennek a változatnak továbbra is hátránya az, hogy minden példányobjektumban megjelenik az összes metódus referenciaszinten. Ennek egyik változata az, amikor a bővítést vegyítik a prototípuslánccal (ld. a funkcionális bővítés alapcikkét). Ekkor a metódusokat egy prototípus-objektumba kompozitálják, az adattagokat pedig mély másolással egy erre hivatkozó példányobjektumba. Ehhez természetesen valamilyen módon szükséges a metódus-prototípusok és adatprototípusok szétválasztása.

Vissza a tartalomjegyzékhez

Magas szintű segédfüggvények objektumok létrehozására

Fentebb láthattuk, hogy a kód-újrahasznosítást alapvetően vagy prototípuslánccal vagy kompozitálással oldhatjuk meg. A fenti műveletek elvégzéséhez készíthetünk segédfüggvényeket, melyek a fenti megoldások felesleges ismétlésétől kímélnek meg.

Egy gyárfüggvény készítéséhez a következő adatokat szükséges megadnunk:

Modellobjektumok

Az alábbi példákban a következő modellobjektumokat fogjuk használni:

Forráskód
var childProto = {
    methods: {
        getName: /*...*/,
        setName: /*...*/
    },
    data: {
        name: /*...*/,
        dateOfBirth: /*...*/
    },
    init: function () {
        //Privát adattag
        var secretNickName = /*...*/;
        //Privilegizált metódusok
        var privilegedMethods = {
            setSecretNickName: /*...*/,
            getSecretNickName: /*...*/
        };
        return extendShallow(this, privilegedMethods);
    }
};
var preschoolProto = {
    methods: {
        getSign: /*...*/,
        setSign: /*...*/,
        getName: /*...*/
    },
    data: {
        sign: 'car'
    },
    super: child
};

Prototípusláncot alkalmazó gyárfüggvények készítése

A fentebb tárgyalt prototípusos öröklést a következőképpen általánosíthatjuk:

Forráskód
var createFactoryWithPrototype = function createFactoryWithPrototype(opts) {
    //Paraméterek kiolvasása
    var methods    = opts.methods || ;
    var publicData = opts.data    || ;
    var _super     = opts.super;
    var init       = opts.init    || function () ;
    var publicMethods = extendShallow(
        Object.create(_super ? _super.methods : Object.prototype),
        methods,
        _super ? {
            _super: _super.methods
        } : 
    );
    return {
        create: function(props) {
            var obj = extendDeep(
                Object.create(publicMethods),
                _super ? _super.create() : ,
                publicData,
                props || 
            );
            return extendFunc(obj, init);
        },
        methods: publicMethods
    };
};
//child gyárfüggvény előállítása
var child = createFactoryWithPrototype(childProto);
var c1 = child.create();
var c2 = child.create();
c1.setSecretNickName('secret1');
c2.setSecretNickName('secret2');
ok( c1.getName === c2.getName, 'A metódusok közösek');
ok( c1.dateOfBirth !== c2.dateOfBirth, 'Adattagok nem közösek');
ok( c1.getSecretNickName() === 'secret1', 'Privát adattag elérhető');
ok( c1.getSecretNickName() !== c2.getSecretNickName(), 'Privát adattagok nem közösek');
//preschool gyárfüggvény előállítása
var preschool = createFactoryWithPrototype(preschoolProto);
var p1 = preschool.create();
var p2 = preschool.create();
p1.setName('Dávid');
ok( p1.name !== undefined, 'Ős tulajdonságai elérhetők');
ok( p1.sign !== undefined, 'Saját tulajdonságok is megvannak');
ok( p1.dateOfBirth !== p2.dateOfBirth, 'Objektumonkénti állapot');
ok( p1.setName === p2.setName, 'Ős metódusai közösek');
ok( p1.setSign === p2.setSign, 'Saját metódusok közösek');
ok( p1.getName() === 'Dávid (preschool)', 'Ősmetódus felülírva és meghívva');

Kompozíciót alkalmazó gyárfüggvények készítése

Kompozíció elkészítéséhez majdnem ugyanezekre a paraméterekre van szükség. Annyi a különbség, hogy itt nincsen szülőobjektum, hiszen a paraméterül megkapott objektumokat vegyíti egybe. Az, hogy hogyan adjuk meg a metódusokat és adattagokat, változhat, mi most az előzőhöz hasonlóan külön adjuk meg őket. Annak érdekében, hogy áttekinthető legyen a paraméterezés, a modellobjektumokat külön definiáljuk (childProto, stb). Az alábbiakban azt a változatot adjuk meg, amikor a metódusok egy külön prototípus-objektumba másolódnak.

Forráskód
var createFactoryWithComposition = function createFactoryWithComposition() {
    var args = arguments;
    var methods = ;
    for (var i = 0; i < arguments.length; i++) {
        extendDeep(methods, args[i].methods || );
    };
    return {
        create: function(props) {
            var obj = Object.create(methods);
            for (var i = 0; i < args.length; i++) {
                extendDeep(obj, args[i].data || );
                extendFunc(obj, args[i].init || function () );
            };
            return extendDeep(obj, props);
        }
    };
};
//child gyárfüggvény előállítása
var child = createFactoryWithComposition(childProto);
//ld. a teszteket a prototípusláncnál
//preschool gyárfüggvény előállítása
var preschool = createFactoryWithComposition(childProto, preschoolProto);
//ld. a teszteket a prototípusláncnál, kivéve a getName felüldefiniálását

Vissza a tartalomjegyzékhez

Objektumok létrehozása klasszikus objektum-orientált szintaxissal

A konstruktorfüggvények

JavaScriptben a fent említett módokon túl lehetőség van a klasszikus objektum-orientált programozáshoz hasonló módon a new operátor segítségével objektumokat létrehozni.

Forráskód
var c = new Child();

A szintaxisbeli hasonlóság ellenére azonban a JavaScriptbeli működés jelentősen eltér. Mivel JavaScriptben nincsenek osztályok, ezért a new operátor operandusául egy függvényt kell megadni, amely a new operátor hatására speciálisan viselkedik, és egy objektumot hoz létre. A new operátorral használandó függvényeket konstruktorfüggvényeknek hívjuk, és speciális mivoltukat konvencionálisan nagy kezdőbetűvel jelezzük. A konstruktorfüggvények new operátorral történő meghívását konstruktorhívási mintának is nevezzük.

Forráskód
//A konstruktorfüggvény
var Child = function Child() {
    this.name = 'Anonymous';
}
//Konstruktorhívási minta
var c = new Child();
ok( c.name === 'Anonymous', 'A létrejött objektum name tulajdonsága elérhető és helyes');

A konstruktorhívás folyamatának háttere

A konstruktorhívás hátterében zajló folyamatok megértéséhez először idézzük fel azokat az ismereteket, amelyeket a függvényekről mint objektumokról ismerünk. Ezek közül számunkra most különösen a függvény prototype tulajdonsága bír nagy jelentőséggel. Minden függvénynek ugyanis automatikusan van egy prototype tulajdonsága, ami egy olyan objektumra mutat, aminek constructor tulajdonsága az adott függvényre hivatkozik.

A függvény és a hozzá tartozó prototype objektum kapcsolatának sematikus ábrázolásaA függvény és a hozzá tartozó prototype objektum kapcsolatának sematikus ábrázolása

A függvény new-val történő hívásakor először egy olyan objektum jön létre a this kulcsszó alatt, amelynek prototípus-objektuma a konstruktorfüggvény prototype tulajdonsága által mutatott objektum. A this objektumhoz a konstruktorfüggvényben további adattagokat és metódusokat adhatunk, majd a függvény impliciten a this objektummal tér vissza. A háttérben zajló folyamatokat a következőképpen szemléltethetjük.

Forráskód
var Child = function Child() {
    //Új objektum létrehozása a this-ben
    var this = Object.create(Child.prototype);
    //További tulajdonságok hozzáadása
    this.name = 'Anonymous';
    //Visszatérés a létrehozott objektummal
    return this;
}
A konstruktorfüggvény és az általa létrehozott objektum kapcsolataA konstruktorfüggvény és az általa létrehozott objektum kapcsolata

A konstruktorhívási minta alkalmazásának hátrányai

EcmaScript 5 előtt csak a függvények prototype tulajdonságán keresztül lehetett egy létrejövő objektum prototípus-objektumát beállítani. Az EcmaScript 5-ös Object.create() metódus azonban ezt feleslegessé teszi.

A konstruktorfüggvény további tulajdonsága az, hogy ha szerepel benne explicit return utasítás, akkor azzal konstruktorhívásuk azzal tér vissza, amit így megadtunk.

Forráskód
var Child = function Child() {
    this.name = 'Anonymous';
    return {
        something: 'else'
    };
}
var c = new Child();
ok( c.name === undefined, 'A name tulajdonság jött létre');
ok( c.something === 'else', 'Helyette az else tulajdonság érhető el');

A konstruktorfüggvények használatának további veszélye az, hogy ha elfelejtjük a new operátorral meghívni, akkor a this a globális objektumra mutat, és így könnyen teleszemetelhetjük a globális névteret. Ennek elkerülésére több minta is van:

Forráskód
var Child = function Child() {
    var that = Object.create(Child.prototype);
    that.name = 'Anonymous';
    return that;
}
var c1 = new Child();
var c2 = Child();
ok( c1.name === 'Anonymous', 'Működik new-val');
ok( c2.name === 'Anonymous', 'Működik new nélkül');
ok( Object.getPrototypeOf(c2) === Child.prototype, 'A prototípus-objektum is jó');
Forráskód
var Child = function Child() {
    if (!(this instanceof Child)) {
        return new Child();
    }
    this.name = 'Anonymous';
}
//Ld. fenti tesztek

A klasszikus objektum-orientált szintaxis használatának további hátránya, hogy használata azt sugallja, hogy a JavaScript nyelvben valamilyen módon mégis osztályokat lehet létrehozni. Mindeközben alternatívát állít olyan megoldásoknak, amelyek a nyelv természetes adottságait használják ki.

Végül a klasszikus objektum-orientált fogalomkör számos olyan megoldást kényszerít a programozóra, amellyel a program elveszíti rugalmasságát, kiterjeszthetőségét. Az öröklés során az utódosztály közvetlen kapcsolatot épít ki a szülőosztállyal, megszüntetve ezzel a két funkcionalitás lazán kapcsolt mivoltát. Nem véletlen, hogy a klasszikus objektum-orientált programozásban használatos tervezési mintákról szóló alapvető szakirodalom is azt hangsúlyozza, hogy inkább a kompozíciót használjuk az öröklés helyett, és maga a könyv is azért jött létre, hogy klasszikus objektum-orientált megközelítés során tapasztalt akadályokra jól bevált megoldásokat dolgozzanak ki. Ezek a megoldások sokszor bonyolultak, JavaScriptben viszont nagyon egyszerűen áthidalhatók.

Az alábbiakban áttekintjük, hogy a fentebb, a JavaScript nyelv dinamikusságát és prototípusosságát kihasználó minták mellett a klasszikus objektum-orientált szemlélettel hogyan oldható meg objektumhierarchiák kialakítása. Tesszük ezt azért, mert a ma feltalálható korszerű függvénykönyvtárak nagy része is ezt a szemléletet követi. Ha használni nem is, de ismernünk tehát mindenféleképpen szükséges ezt a módszert is.

Gyárfüggvények

A konstruktorfüggvény objektumok létrehozására valók, és mint ilyenek gyárfüggvények is egyben. Korábbi példánk így néz ki konstruktorfüggvényekkel.

Forráskód
var Child = function () {
    this.name = 'Anonymous';
    this.dateOfBirth = {
        year: 1970,
        month: 1,
        day: 1
    };
    this.getName = function getName() {
        return this.name;
    };
    this.setName = function setName(name) {
        this.name = name;
    };
};

Paraméteres gyárfüggvények

Paraméterek megadhatók egyesével, de átadható egy konfigurációs objektum is. Ekkor a this-t először az alapértelmezett értékekkel bővítjük, majd a konfigurációs objektummal.

Forráskód
var Child = function (props) {
    extendDeep(this, {
        name:        /*...*/,
        dateOfBirth: /*...*/,
        getName:     /*...*/,
        setName:     /*...*/
    }, props || );
};

Privát adattagok és privilegizált metódusok

Természetesen itt is van lehetőség privát adattagok létrehozására. Maga a konstruktorfüggvény biztosítja a closure-t ezeknek az elhelyezésére.

Forráskód
var Child = function (props) {
    //Privát adattag
    var secretNickName = '';
    extendDeep(this, {
        name:        /*...*/,
        dateOfBirth: /*...*/,
        getName:     /*...*/,
        setName:     /*...*/,
        //Privilegizált metódusok
        setSecretNickName: function (name) {
            secretNickName = name;
        },
        getSecretNickName: function () {
            return secretNickName;
        }
    }, props || );
};

Metódusok hatékony tárolása

Metódusok hatékony tárolását tipikusan a prototípus-objektumban szokták elvégezni.

Forráskód
var Child = function (props) {
    extendDeep(this, {
        name:        /*...*/,
        dateOfBirth: /*...*/        
    }, props || );
};
extendShallow(Child.prototype, {
    getName:     /*...*/,
    setName:     /*...*/
});

Természetesen itt sincs lehetőség a privát változókat elérő privilegizált metódusok prototípus-objektumban történő tárolására.

Becsomagolás „osztály-modulba”

Gyakran látni olyan megoldást, amikor az előző példabeli létrehozást még egy további önkioldó függvénybe csomagolják. Ekkor a konstruktorfüggvényhez tartozó logika mind egy helyen van, és véletlenül sem szivárog a külső névterekbe. (Ezt a megoldást alkalmazza a TypeScript és a CoffeeScript fordító is.)

Forráskód
var Child = (function () {
    var Child = function (props) {
        extendDeep(this, {
            name:        /*...*/,
            dateOfBirth: /*...*/        
        }, props || );
    };
    extendShallow(Child.prototype, {
        getName:     /*...*/,
        setName:     /*...*/
    });
    return Child;
})();

Öröklés

A klasszikus objektum-orientált szintaxist utánozó megközelítésben is a korábban tárgyalt objektumhierarchia kialakítása a cél: a gyerekobjektumba kell másolni a gyerek és a szülő adattagjait, prototípusában a gyerek metódusai, annak prototípusában pedig a szülő metódusai kapnak helyet. Ennek kialakításában segít az inherit() segédfüggvény. (Ebben használhatnánk az Object.create() metódust is, de szándékosan azt a formát tartottuk meg, amely nagyobb függvénykönyvtárakban megtalálható, és ami működik EcmaScript 3-ban is.)

Forráskód
//Objektumhierarchiát kialakító függvény
var inherit = function (C, P) {
    var F = function () ;
    F.prototype = P.prototype;
    C.prototype = new F();
    C.prototype._super = P.prototype;
    C.prototype.constructor = C;
};
//Gyerekkonstruktor készítése
var Preschool = (function (_super) {
    var Preschool = function (props) {
        extendFunc(this, _super);
        //vagy paramétert is átadva:
        //_super.call(this, props)
        extendDeep(this, {
            sign: 'default sign'
        }, props || );
    };
    inherit(Preschool, _super);
    extendShallow(Preschool.prototype, {
        getSign: function getSign() {
            return this.sign;
        },
        setSign: function setSign(sign) {
            this.sign = sign;
        },
        getName: function getName() {
            var name = this._super.getName.call(this);
            return name + ' (preschool)';
        }
    });
    return Preschool;
})(Child);

Vissza a tartalomjegyzékhez

EcmaScript 6 újdonságok az objektumkezelésben

Fentebb láthattuk, hogy EcmaScript 5-ben több lehetőségünk is van objektumgenerátorokat létrehozni. A klasszikus objektum-orientált nyelvekben az adattagok és metódusok egységbe zárását az osztályok végzik el. JavaScriptben számos függvénykönyvtár próbálta valamilyen módon szimulálni a klasszikus osztály működését. A nagy gond ezekkel azonban az, hogy működésükhöz az adott függvénykönyvtár szükséges, és az egyes megvalósítások nem cserélhetőek fel egymással.

Látva ezt a problémát, az EcmaScript szabvány következő verziójában nyelvi szinten próbálják az adatok és metódusok egységbe zárását megoldani. Ehhez a klasszikus OOP nyelvek osztály megnevezését használják fel, utalva egyrészt arra, hogy ezek ilyen tulajdonságú objektumok létrehozására szolgálnak, másrészt továbbra is megkönnyítve a klasszikus OOP-n nevelkedett programozók számára a konstrukció megértését.

Fontos azonban tudnunk azt, hogy JavaScriptben továbbra sem lesznek a klasszikus értelemben vett osztályok. A class kulcsszóval létrehozott konstrukciók továbbra is objektumokká képződnek. A class kulcsszó igazából nem is nyelvi újdonság, hanem csak egy olyan szintaktikai kiegészítés, amellyel az objektumgenerátorok eddig körülményes készítése egyszerűbbé válik, de amely a háttérben az eddigi nyelvi megoldásokra képződik le (syntactic sugar). A leképzett szintaxis általában az előző fejezetben tárgyalt klasszikus OOP-re hasonlító megoldás, azaz függvényekre és azok prototype-jára vezetik vissza. Igazából az mindegy is, hogyan valósítják meg, a lényeg, hogy a metódusok a prototípus-objektumban szerepelnek, az adattagok pedig az új objektumban jönnek létre.

Íme példaképpen a Child objektumgenerálónk az EcmaScript 6 tervezete szerint (most a name adattaghoz gettert és settert hozunk létre):

Forráskód
class Child {
    constructor(name, dateOfBirth) {
        this._name = name;
        this.dateOfBirth = 100;
    }
    say(something) {
        return this.name + ' says: ' + something;
    }
    get name() { 
        return this._name; 
    }
    set name(value) {
        if (value === '') {
            throw new Error('Name cannot be empty.');
        }
        this._name = value;
    }
}

Természetesen lehetőség van öröklésre is. Erre az extends kulcsszó szolgál, és a metódusokon belül a super objektumon keresztül hivatkozhatunk a szülő objektum metódusaira.

Forráskód
class Preschool extends Child {
    constructor(name, dateOfBirth, sign) {
        super(name, dateOfBirth);
        this._sign = sign;
    }
    get sign() {
        return this._sign; 
    }
    set sign(value) {
        this._sign = value;
    }
    get name() { 
        return super.name + ' (preschool)';
    }
}

Az EcmaScript 6-os újdonságokat a legújabb böngészőkben, a Traceur, illetve a TypeScript fordító segítségével lehet kipróbálni.

Objektum-orientált minták JavaScriptben

Flash lejátszó letöltése

Objektum-orientált minták JavaScriptben

Vissza a tartalomjegyzékhez

Új Széchenyi terv
A projekt az Európai Unió támogatásával, az Európai Szociális Alap társfinanszirozásával valósul meg.

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.