Obiecte, proprietăţi, metode (în Javascript)

Un obiect are asociate proprietăţi şi metode; o proprietate a unui obiect poate fi accesată prin "operatorul punct": objectName.propertyName; se poate adăuga obiectului o proprietate, indicând un nume şi asignând o valoare: objectName.newPropertyName = value; analog se poate adăuga o nouă metodă (funcţie): objectName.myMethod = function() {...};. Pentru exemplu:

<script type="text/javascript">
var x = Math.PI;         // proprietatea .PI a obiectului Math  
alert ( Math.sin(x/6) ); // metoda .sin() a obiectului Math
// extinde Math cu o metodă de calcul a secantei trigonometrice:      
Math.secanta = function(x) { return 1 / Math.cos(x); };
alert ( Math.secanta(x/3) ); 
</script>
  

Un obiect poate fi instanţiat în program folosind operatorul new: var unObArr = new Array(); obiectul creat astfel moşteneşte toate proprietăţile şi metodele obiectului din care a fost instanţiat prin new, precum şi pe cele ale prototipurilor acestuia. Există o proprietate (predefinită) care este comună tuturor obiectelor (exceptând Math), anume .prototype; aceasta serveşte în Javascript pentru extinderea obiectelor (pe parcursul execuţiei).

Tablouri în Javascript

În Javascript, tablourile sunt obiecte Array: conţin nu doar valorile propriu-zise (accesibile prin []), dar şi diverse proprietăţi şi metode (accesibile prin "operatorul de selecţie" .); de exemplu:

var unArr = new Array();  // altă posibilitate: var unArr = [];
unArr[0] = 123;
unArr.push(5, 6, 7);  // invocă metoda .push() adăugând alte elemente
unArr[5] = "un şir ..."; // unArr[4] are valoarea "undefined"
unArr.flag = true;  // adaugă proprietatea '.flag' (cu valoarea 'true')
alert(unArr + '\nLungimea tabloului: ' + unArr.length);
  

Executând acest script, se verifică faptul că un Array poate conţine valori de diverse tipuri (numere, întregi sau nu; şiruri de caractere; valori undefined; etc.); nu este necesar să se precizeze numărul de elemente (valoare menţinută în proprietatea .length a obiectului).

Pe lângă metodele predefinite (.push(), .pop(), .join() etc.; vezi Core Javascript 1.5) - pot fi adăugate unui obiect Array şi metode noi:

    var unArr = [ 1, 2, 3, 'foo', 'bar', 3.1415 ]; 
    unArr.print = function() { 
               alert( this.join(' / ') + '\nlungime: ' + this.length ); 
    };
    unArr.print();
    var altArr = [1,2,3,4,5,6,7,8,9];
    altArr.print();  // print() nu este vizibilă şi din altArr
  

Astfel mai sus, am adăugat obiectului unArr metoda .print(); aceasta nu este însă accesibilă şi din obiectul altArr, creat ulterior. Pentru ca metoda să fie vizibilă şi altora dintre obiectele Array, ea trebuie definită pentru însuşi obiectul Array, folosind prototype:

    Array.prototype.print = function() { 
    alert(this.join(' / ') + '\nlungime: ' + this.length); };
    var unArr = [ 1, 2, 3, 'foo', 'bar']; 
    unArr.print();
    [1,2,3,4,5,6,7,8,9].print();    
  

Să precizăm că this este o referinţă la însuşi obiectul pentru care se va invoca metoda (aici, la unArr şi respectiv la tabloul anonim [1,2,..., 9]).

Metoda sort şi… q_sort (?)

Obiectul Array are metoda nativă sort():
["Foo", "Bar", "bar", "Baz"].sort()
va produce tabloul ordonat lexicografic ["Bar", "Baz", "Foo", "bar"];
[30, 7, 300, 31].sort()
produce tabloul ordonat lexicografic [30, 300, 31, 7];
[30, 7, 300, 31].sort( function(a,b) { return a - b; } )
va produce tabloul ordonat numeric [7, 30, 31, 300].

În ultimul exemplu, metodei sort() i s-a transmis ca parametru o funcţie de comparare a elementelor; fiindcă a — b nu se poate interpreta altfel decât ca "scădere a două numere", elementele tabloului sunt considerate ca numere (încât se abandonează ordonarea implicită - cea lexicografică). În Javascript, funcţiile sunt şi ele nişte obiecte - putând fi utilizate la fel ca oricare variabile (inclusiv ca parametri pentru alte funcţii).

Intenţia noastră aici ar fi aceea de a extinde Array cu o metodă de sortare bazată pe "QuickSort"; dar trebuie să observăm că o asemenea "extindere" ar putea avea doar raţiuni didactice… De exemplu, căutând la code.google.com/p/v8 putem vedea că de fapt sort este implementată folosind şi "QuickSort" (cel puţin, pentru browserul Chrome). Putem adăuga totuşi o metodă .q_sort (transcriind în fond un program prezentat în multe manuale de C) astfel:

Array.prototype.q_sort = function (beg, end) {
    var at_beg = beg;
    var at_end = end;
    var pivot = this[beg];
    while (beg < end) {
        while ((this[end] >= pivot) && (beg < end)) 
            end--; 
        if (beg != end) 
            this[beg++] = this[end];
        while ((this[beg] <= pivot) && (beg < end)) 
            beg++;
        if (beg != end) 
            this[end--] = this[beg];
    }
    this[beg] = pivot;
    if (at_beg < beg) this.q_sort(at_beg, beg - 1);
    if (at_end > beg) this.q_sort(beg + 1, at_end);
}
    

Pentru testare desigur, putem folosi de exemplu:

  var lista = ['Ionescu','Ababei','Popescu','Barbu'];
  lista.q_sort(0, lista.length - 1);
  alert(lista); // numele, în ordine alfabetică
  var listn = new Array();
  for(var i=0; i< 100; i++) listn[i] = Math.round(100*Math.random());
  listn.q_sort(0, listn.length - 1);
  alert(listn); // numerele, ordonate crescător

q_sort ordonează corect şi numerele, fără să implicăm o funcţie de comparare ca în cazul metodei native sort; dar acest fapt nu-i neapărat un avantaj: implicarea unei funcţii de comparare a elementelor creează posibilitatea de a folosi sort şi pe tablouri mai complicate, de exemplu având drept elemente alte tablouri. Şirurile de caractere sunt ordonate corect (lexicografic), dar numai dacă e vorba de caractere ASCII standard - altfel (ca şi în cazul sort) este necesară implicarea unei funcţii de comparare.

În finalul acestui mic exerciţiu de prezentare a unor aspecte de lucru cu obiecte Javascript, trebuie să subliniem că adăugarea unei noi metode la un obiect predefinit trebuie făcută cu prudenţă şi numai dacă într-adevăr este nevoie; nu are rost - decât cel mult "didactic", ca în cazul de aici - să adaugi o metodă (precum q_sort-ul nostru) care de fapt se cam suprapune peste una existentă (peste sort, în cazul de faţă).