Spuneam in articolul precedent ca in mare parte nu exista asemanari intre JavaScript si Java, in afara de numele celor doua. Acest lucru ramane valabil si atunci cand vorbim de obiecte, clase si mostenire. In timp ce Java este un limbaj de programare orientat pe obiecte, JavaScript este un limbaj de programare bazat pe obiecte (eng. object-based sau prototype-based).
alert((123.4567).toPrecision(2));
La fel, daca vrem sa impartim de exemplu un sir de caractere in cuvinte (presupunand ca despartirea cuvintelor este facuta doar prin spatii):
var cuvinte = "unu doi trei".split(' ');
// cuvinte = ["unu", "doi", "trei"]
Invers, daca vrem sa “lipim” un vector de cuvinte intr-o propozitie (la fel ca mai sus, cuvintele despartite intre ele printr-un singur spatiu):
var propozitie = ["Ana", "are", "mere"].join(' ');
// propozitie = "Ana are mere";
Pentru mai multe despre metodele obiectelor Array, String si Number, puteti consulta urmatoarele 3 articole:
- The Complete Javascript Number Reference
- The Complete Javascript Strings Reference
- Mastering Javascript Arrays
Aceste clase sunt predefinite in JavaScript, dar ce facem daca vrem sa ne definim propriile noastre obiecte? In JavaScript, toate obiectele sunt derivate din Object, care este un obiect generic, parintele tuturor obiectelor. Asa ca evident, cel mai simplu caz este cel cand vrem sa definim o instanta de Object. Este foarte simplu:
var o = new Object();
Dar pe cat de simplu, pe atat de inutil este acest obiect. Orice obiect care este instanta a lui Object, nu are implicit nicio proprietate si nicio metoda. Pentru a ne fi util, putem sa ii adaugam cateva atribute si cateva metode:
o.foo = "bar";
o.baz = function() { alert("Foo: " + this.foo); };
o.baz(); // "Foo: bar"
Cred ca deja ati observat acel this.foo. Care este scopul lui this?
Sa incercam mai intai sa vedem ce se intampla daca nu punem this. Asta inseamna ca o.baz ar arata asa:
o.baz = function() { alert("Foo:" + foo); };
Daca vom incerca sa apelam o.baz(), vom obtine o eroare. De ce? Simplu: nu exista variabila globala foo. Daca am fi declarat inainte o variabila globala foo:
foo = "bat";
Atunci la apelarea o.baz() vom obtine “Foo: bat”. Nu e chiar ce ne doream; pana la urma, majoritatea metodelor unui obiect vor actiona asupra membrilor acelui obiect. Aici ne ajuta this. this este de fapt o referinta la obiectul in contextul caruia se executa functia. In mode normal, pentru o functie globala, this==window, iar pentru o metoda a unui obiect o, this==o. Observati ca am spus “in mod normal”. Asta pentru ca avem posibilitatea sa setam this la orice instanta de obiect dorim, prin intermediul metodei Function.call(). care are semnatura:
Function.call(obiect_care_va_fi_referit_de_this, vector_de_parametrii_transmisi_functiei);
Vectorul de parametrii transmisi functiei este optional. Sa incercam un exemplu:
//context global
var foo = function(bar) { alert("Bar = " + bar + ", bat = " + this.bat); };
var bat = "context global";
var o = new Object();
o.bat = "contextul lui o";
o.foo = foo;
//testam
foo("bar"); // "Bar = bar, bat = context global"
o.foo("bar"); // "Bar = bar, bat = contextul lui o"
foo.call(o, "bar"); // "Bar = bar, bat = contextul lui o"
// Lista de parametri este optionala, si orice parametru
// al unei functii care nu este transmis, va fi undefined
foo.call(o); // "Bar = undefined, bat = contextul lui o"
Exista o metoda chiar mai simpla de a declara un Object, sau o instanta a lui Object, cu ajutorul notatiei literale a obiectelor (eng. object literal notation):
var o = {}; // echivalent cu o = new Object();
var o = {
foo: "bar",
baz: function() {
alert(this.foo);
}
}; // echivalent cu prima declaratie a lui o, de mai sus
De notat ca obiectele in JavaScript pot fi privite ca vectori asociativi (notiune familiara programatorilor PHP, si chiar mai familiara programatorilor Python (pentru ca sintaxa este asemanatoare) – obiectele JavaScript sunt foarte asemanatoare dictionarelor Python, sau programatorilor Java – clasa Hashtable). Astfel, orice proprietate si orice metoda a unui obiect poate fi referentiata in doua moduri:
o.foo; o["foo"];
Dar a doua metoda de referentiere este mai puternica, pentru ca putem declara membri de obiecte care sa aiba nume care nu sunt identificatori valizi de nume de variabile:
o["o variabila"] = "o valoare"; // corect o."o variabila" = "o valoare"; // gresit o.o variabila = "o valoare"; // gresit
Putem itera prin membrii unui obiect cu instructiunea for .. in
for (membru in o)
alert(membru + ": " + o[membru]);
Atentie, atunci cand folosim notatia literala, numele cheilor (ceea ce apare in stanga definitiei), sunt de fapt siruri de caractere, doar ca atunci cand vorbim de chei care sunt si identificatori valizi, putem sa omitem ghilimelele. Daca folosim obiectul ca pe un vector asociativ insa, atunci cand folosim identificator, va fi folosita valoarea variabilei care sta pe post de cheie. De exemplu:
var foo = "bar";
var o1 = {
foo: "bar" // de fapt, este vorba de "foo": "bar"
};
alert(o1.foo); // "bar"
alert(o1.bar); // undefined
var o2 = {};
o2[foo] = bar; // foo == "bar", deci de fapt o2["bar"] = "bar"
alert(o2.foo); // undefined
alert(o2.bar);
In JavaScript, nu exista notiunea de clasa. Si atunci, cum putem defini un obiect, altul decat Object, care sa poata fi instantiat? Sa presupunem ca vrem sa definim un obiect Foo, apoi sa instantiem de doua ori acest obiect. Incepem prin a defini o functie Foo:
function Foo(bar) {
this.bar = bar;
}
Nota Nu este obligatoriu ca numele de obiecte sa inceapa cu litera mare, dar este o practica comuna, intrucat este usor de facut diferenta intre o declaratie de functie si o declaratie de Obiect care poate fi instantiat.
Cum instantiem aceasta clasa?
var foo1 = new Foo("bar1");
Observati cuvantul cheie new. Cu ajutorul lui pot fi instantiate obiectele, altfel apelul Foo("bar") ar fi avut ca efect doar apelul functiei Foo, iar foo1 ar fi fost undefined. Sa vedem un exemplu care sa exemplifice toate acestea:
bar = "test";
function Foo(bar) {
this.bar = bar;
}
alert(bar); // "test"
var f = Foo("ceva");
// nu am creat o instanta, ci doar am apelat functia
// in plus, this==window, deci bar se refera la variabila globala bar
alert(bar); // "ceva"
alert(f); // undefined
// cream o instanta a lui Foo
// de data aceasta, this se va referi la noua instanta
// iar this.bar=bar va crea un membru bar al instantei,
// pe care il va initializa cu parametrul transmis
var f1 = new Foo("baz");
//de data aceasta,f1 este obiect
alert(f1); // [object Object]
alert(f1.bar); // "baz"
// variabila globala bar a ramas neschimbata
alert(bar); // "ceva"
Acum, sa cream doua instante ale lui Foo:
var foo1 = new Foo("bar1");
var foo2 = new Foo("bar2");
Sa presupunem ca, dupa ce am declarat obiectul Foo, vrem sa adaugam o metoda getBar() care sa ne arate valoarea membrului bar al unei instante din Foo.
Continuand cu obiectul Foo din exemplul anterior, sa incercam urmatoarul cod:
foo1.getBar = function() { alert(this.bar); };
foo1.getBar(); // "bar1"
foo2.getBar(); // eroare!
Ce s-a intamplat? Prima linie de cod de mai sus adauga intr-adevar metoda getBar, dar numai pentru instanta foo1 a lui Foo. Deci foo2 nu are o metoda getBar, si atunci a treia linie de cod va da eroare.
Si atunci, ce este de facut? Sa ne intoarcem la definitia lui Foo si sa-i adaugam aceasta metoda? Am putea, mai ales pentru acest exemplu simplu, dar nu este intotdeauna atat de simplu. Daca folosim o biblioteca JavaScript, este destul de dificil sa cautam in codul acesteia si sa adaugam unor obiecte, metodele de care avem nevoie. Iar daca la un moment dat apare o versiune mai noua a bibliotecii, va trebui sa modificam de fiecare data codul din biblioteca respectiva (nu am invatat inca mecanisme de mostenire). Nu este prea elegant, si chiar deloc recomandat.
Insa JavaScript are o caracteristica foarte puternica. Tineti minte ca la inceputul articolului spuneam ca JavaScript este un limbaj prototype-based? In JavaScript, orice obiect are un membru care are numele prototype. Ce face acest membru atat de puternic, este faptul ca orice membru sau metoda i-am adauga, acestea devin disponibile oricarei instante a acelui obiect, chiar si instantelor deja create. Foarte puternic, intr-adevar. Revenind la exemplul cu clasa Foo, acum ne este foarte simplu sa adaugam metoda getBar ambelor instante foo1 si foo2 (ca de altfel oricaror alte instante, deja create sau viitoare):
var foo1 = new Foo("bar1");
var foo2 = new Foo("bar2");
// fa operatii cu foo1 si foo2
Foo.prototype.getBar = function() { alert(this.bar); };
foo1.getBar(); // "bar1"
foo2.getBar(); // "bar2"
var foo3 = new Foo("bar3");
foo3.getBar(); // "bar3"
Destul de interesant, nu-i asa? Prin astfel de metode unele framework-uri JavaScript (de care vom vorbi intr-un articol viitor) au devenit foarte populare (mai ales Prototype, primul framework de JavaScript care a devenit cu adevarat popular, si care isi ia numele chiar de la aceasta caracteristica a limbajului).
Poate exemplele de pana acum nu au fost chiar practice, dar acum ca stim aceasta caracteristica (puternica) a limbajului, putem sa implementam multe metode utile. De exemplu, daca avem nevoie sa gasim elementele pare ale unui vector, in mod repetat. Putem adauga prototipului obiectului Array o metoda evens, care sa intoarca un vector cu elementele pare:
Array.prototype.evens = function() {
var v = []; // sau v = new Array();
for (i = 0; i < this.length; i++)
if (this[i] % 2 == 0)
v.push(this[i]);
return v;
}
var pare = [1,2,3,4,5,6].evens(); // [2,4,6]
Sau poate vrem doar elementele de pe pozitiile pare:
Array.prototype.evenIndexes = function() {
var v = [];
for (i = 0; i < this.length; i += 2)
v.push(this[i]);
return v;
}
var pozitii_pare = [1,2,3,4,5,6].evenIndexes(); // [1,3,5]
Dar daca vrem sa implementam un mecanism de mostenire, in loc sa adaugam atribute si proprietati obiectelor deja existente? Simplu, folosim tot prototype:
function Foo(parametri) { ... }
Foo.metoda = function(parametri) { ... }
function Bar(parametri) { ... }
Bar.prototype = new Foo(); // mostenirea tuturor membrilor lui Foo
Bar.prototype.constructor = Bar; // membrul constructor al lui prototype
// este de fapt constructorul instantelor,
// iar in cazul nostru vrem alt constructor decat cel al lui Foo
Bar.prototype.metodaSpecificaLuiBar = function(parametri) { ... }
Simplu, nu?
Singurul lucru caruia ii mai simtim lipsa daca suntem obisnuiti cu un limbaj de programare OOP, este declararea variabilelor si metodelor private (pentru ca publice au fost toate de pana acum). Exista cateva metode, voi prezenta una dintre ele (care foloseste inchideri (closures), despre care am vorbit in articolul precedent.
function Foo(parametri) {
var variabila_privata = 0;
function functie_privata() { ... };
return {
membru_public: valoare,
getVariabilaPrivata: function() { return variabila_privata; },
// ...
};
}
Ce se intampla de fapt in acest cod? Definim cateva variabile locale in constructorul obiectului, dar returnam de fapt un alt obiect in care punem membrii care dorim sa fie publici. Deoarece JavaScript suporta inchideri, variabilele locale din constructor continua sa fie accesibile functiilor interne care fac referinta la ele, chiar dupa ce constructorul si-a continuat executia, insa nu avem acces la ele din afara constructorului: obiectul returnat nu contine un membru variabila_privata. Astfel, dupa ce am apela
var foo1 = new Foo(parametri); alert(foo1.variabila_privata); // eroare! nu am returnat niciun membru cu numele variabila_privata // variabila locala variabila_privata care fusese declarata in Foo() // continua sa fie accesibila metodei getVariabilaPrivata() alert(foo1.getVariabilaPrivata()); // functioneaza!
Cu aceasta am acoperit cele mai importante puncte din lucrul cu obiecte si instante in JavaScript. Intr-un articol viitor, vom vedea ce solutii ofera unele framework-uri de JavaScript pentru a ne usura implementarea mostenirii.
Data viitoare, vom vorbi despre manipularea elementelor DOM dintr-o pagina HTML.
Link-uri utile:

The Obiecte in JavaScript by Interfeţe Web, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
Pingback: Manipularea DOM cu JavaScript | Interfeţe Web
Pingback: Framework-uri JavaScript: Clase in MooTools | Interfeţe Web