3.curs JavaScript
3.curs JavaScript
Babeș-Bolyai,
Facultatea de Matematică și Informatică
Lect. dr. Darius Bufnea
Notițe de curs Programare Web: JavaScript
Pe lângă prezentul material legat de limbajul JavaScript, studenți sunt rugați „ferm” să parcurgă si
următorul material: http://www.w3schools.com/js/default.asp (toate secțiunile din stânga până la JS
AJAX (fără JS AJAX) plus JS Examples).
JavaScript s-a născut ca un limbaj interpretat client-side, inițial destinat browser-elor. A fost dezvoltat de
Brendan Eich de la Netscape (Netscape fiind considerat bunicul „Firefox”), inițial denumit LiveScript. A
apărut în decembrie 1995, sub denumirea de JavaScript, denumire data în urma unui agreement între
Sun Microsystems (inventatoarea Java) și Netscape.
Paranteză: La acea vreme Sun dorea să își popularizeze applet-urile Java ca tehnologie client-side (in
confruntare directă cu Macromedia Flash) și pentru acest lucru acordul cu Netscape prevedea ca
browser-ul celor de la Netscape (Netscape Navigator) să suporte mașina virtuala Java care să permite
rularea applet-urilor Java, iar in schimb Netscape să poate folosi in denumirea limbajului cuvantul „Java”
pentru a face limbajul ce avea să fie cunoscut mai târziu sub denumirea de JavaScript mai popular. In
timp, applet-urile Java au pierdut lupta cu Macromedia Flash aceasta din urmă impunându-se ca și
tehnologie pe frontend, iar și mai tarziu Flash-ul (tehnologie ajunsa între timp in ograda Adobe) a
pierdut definitiv „războiul” in favoarea JavaScript.
La început limbajul a fost destul de nestandardizat, existând mai multe variante, printre cele mai
populare numărându-se:
Toate aceste „variante” (sau dialecte) s-au dovedit a fi o corvoadă pentru programator – nu erau rare
situațiile in care trebuiau scrise variante de „script” diferite pentru a suporta multiple browsere (spre
exemplu o variantă de script pentru Internet Explorer și o varianta de script pentru Netscape Navigator).
S-a ajuns la situația / nevoia de a standardiza acest limbaj în ceea ce se numește ECMAScript.
Deși inițial limbajul a fost gândit pentru a fi folosit client-side (adică a fi rulat de către browser-e), in
ultimul timp se folosește și ca tehnologie pe backend (NodeJS).
Ce este / ce nu este și ce se poate face in JavaScript?
Ce este:
Ce permite:
Ce nu este JavaScript:
- Nu este un limbaj înrudit cu limbajul Java - in afara de nume, singura legătura este sintaxa C
comună specifică ambelor limbaje;
- nu este un limbaj de programare strong typed, e weakly typed - o variabilă poate primi inițial ca
valoare un număr întreg, iar ulterior un string (în timp ce JavaScript e considerat weakly typed,
Java este strongly type).
Codul JavaScript în interiorul documentului HTML se inserează cu ajutorului tag-ului script, fie in
interiorul tag-ului, fie specificat cu ajutorul atributului src a acestui tag:
<script type="text/javascript">
… cod JavaScript …
</script>
sau
Observații:
- atributul src poate indica spre un fișier local cu extensia .js (găzduit în același loc ca și
documentul HTML) sau spre un URL absolut (care începe cu http:// sau https://);
- dacă, codul JavaScript se specifică cu ajutorul atributului src, nu uitați să închideți tag-ul script
(trebuie sa apară și marcajul de sfârșit de tag, fiind un tag cu corp).
In unele situații este util după folosirea tag-ului script, folosirea tag-ului noscript, util in situații în care
browser-ul nu suportă JavaScript din diverse motive – puține probabile în prezent (browser vechi,
browser pentru dispozitive mobile mai vechi, engine-ul JavaScript al browserului este dezactivat).
Exemplu:
<script>
alert("Hello World!");
</script>
<noscript>I can't say hello because your browser doesn't support
JavaScript!</noscript>
O practică des întâlnită pentru a preîntâmpina unele erori pe unele browser-e incapabile sa ruleze cod
JavaScript este plasarea codului JavaScript în interiorul unui comentariu HTML <!-- -->. Exemplu:
<script type="text/javascript">
<!--
// începutul codului JavaScript
alert("Hello World!");
/*
sfârșitul codului JavaScript
*/
-->
</script>
In mod tradițional, codul JavaScript era plasat în cadrul unui tag <script> in secțiunea
<head></head> a documentului HTML. Acest lucru nu este obligatoriu, tag-ul <script> putând fi
plasat oriunde in cadrul documentului.
IMPORTANT: CAT TIMP BROWSER-UL EXECUTA COD JAVA SCRIPT NU „FACE NIMIC ALTCEVA” – NU
(RE)RANDEAZA PAGINA, NU INTERACTIONEAZA CU UTILIZATORUL (NU RAPSUNDE LA COMENZI/CLICK-
URI DE MOUSE ETC).
Exemplu:
Exemplul 1:
<html><head><title>Exemplul 1</title>
<script type="text/javascript">
function clickMe() { // executia functiei nu are loc acum
alert('Hello world');
}
</script>
</head>
<body>
<a href="javascript:clickMe();">Click here</a>
</body></html>
Exemplul 2:
<html>
<head><title>Exemplul 2 JavaScript</title></head>
<body>
Astazi este:
<script type="text/javascript">
document.write(new Date()); // executia are loc acum
</script>
</body>
</html>
<script>
function sum(a, b) {
return a + b;
}
alert(sum(1,2));
</script>
Returnarea unei valori se face cu return (asemănător cu C/C++/Java), iar în lista parametrilor formali (a
și b în exemplul de mai sus) aceștia nu trebuie sa aibă declarat un tip.
element.onclick = function () {
alert('Aceasta este o functie anonima');
}
<script>
(function (a, b) { // a si b sunt parametrii formali ai functiei
// functie anonima care afiseaza suma a doua numere
var s = a + b;
alert(s);
})(2, 3); // apelul cu parametrii actuali ai functiei
</script>
Variabilele JavaScript se declară cu cuvântul rezervat var. Tipul acestora este nedefinit (de fapt tipul
unei variabile este dat de tipul valorii asociate acesteia). Astfel, o variabilă poate primi la un moment dat
ca și valoare un număr, iar ulterior un șir de caractere – JavaScript fiind considerat un limbaj weakly și
dynamically typed.
Exemplu:
var i = 7;
i = 'Ana are mere';
var s = "Cocosul canta";
// șirurile de caractere pot fi delimitate atât cu ' cat și cu "
s = 3.1415;
var c = true;
Printre tipurile de valori pe care le pot fi atribute unei variabile sunt: number, string, boolean,
object, function (funcțiile sunt de fapt niște obiecte mai speciale), undefined.
Exercițiu: Pentru a vă familiariza cu tipurile din JavaScript, operatorul typeof, precum și cu câteva
funcții de conversie precum eval(), Number(), String(), puteți încerca să rulați următoarele linii de
cod în consola JavaScript a browserului preferat. Consola JavaScript este accesibilă sub forma unui tab
separat în cadrul Developer Tools-ului din cadrul browserului (F12 în orice browser – dar mă aștept să
știți acest lucru daca ați făcut debugging la laboratorul de CSS ).
typeof 1
typeof 1.5
typeof '1.5'
typeof eval('1.5')
typeof Number('1.5')
typeof String(2)
typeof 'Ana are mere'
typeof "Cocosul canta"
typeof true
typeof {}
typeof x
f = function (){ return 2;}
typeof f
typeof [1, 2, 3, 4, 5]
punct = { x: 7, y: 9}
typeof punct
1/0
typeof Infinity
typeof 1/0
Întrebare: Dacă 1/0 este Infinity și typeof Infinity este number, de ce typeof 1/0 este NaN
(Not a Number)? Si dacă asta vi se pare simpla , găsiți alte „problemuțe drăguțe” specifice limbajului
JavaScript aici.
In unele contexte, folosirea funcției eval este periculoasă (este posibil sa primiți un mesaj de eroare la
execuția ei). eval în JavaScript face mult mai mult decât să convertească un string la număr, eval poate
sa evalueze inclusiv o secvență de cod (adică să o execute).
Exemplu:
Observații:
- Variabilele pot fi declarate în JavaScript și cu cuvântul rezervat let, mai multe despre acesta
mai târziu in acest material.
- exista limbaje „derivate” din JavaScript (sau mai degrabă construite peste JavaScript), a căror
cod se compilează/translatează în cod JavaScript și care sunt strongly typed. Un exemplu in
acest sens este TypeScript.
Tablouri in JavaScript
Tablourile in JavaScript sunt de fapt niște obiecte mai speciale. Prezentam mai jos câteva modalități de
declarare a acestora:
Observații:
lungimea unui tablou (Array) poate fi aflată prin intermediul proprietății .length a unui array.
Atenție, aceasta se comporta ca o dată membră publică (în accepțiunea OOP), nu ca o metodă
ce se va invoca cu: tari.length() – (paranteză: de fapt proprietatea .length este
implementată folosind o metodă getter – mai multe detalii despre metodele getter și setter in
JavaScript găsiți aici).
Observații comportamentul diferit a constructorului new Array() in funcție de numărul de
parametrii. Apelat cu un parametru, new Array(11) declară un tablou cu 11 elemente
neinițializate (undefined), apelat cu mai mulți parametrii, spre exemplu new Array(5, 6,
7, 8) declară un Array cu 4 elemente inițializate cu valorile specificate;
elementele unui Array pot sa fie de tipuri diferite. Exemplu:
Tablouri multidimensionale
JavaScript acceptă și tablouri multidimensionale. Spre exemplu, o matrice de 3x3 cu elemente întregi
(tablou bidimensional) poate fi declarată astfel:
M = new Array(new Array(1, 2, 3), new Array(4, 5, 6), new Array(7, 8, 9));
// M.length va avea lungimea 3
// M[1][1] va avea valoarea 5
Elementele unui tablou multidimensional se accesează conform notației clasice folosind indecși numerici
din limbajele C/C++/Java (spre exemplu M[i][j]). Pe exemplu de mai sus M este de fapt un array cu 3
elemente, fiecare element la rândul sau fiind un array cu alte 3 elemente.
API-ul JavaScript oferă o serie de operații ce se pot efectua pe un Array. Lista completă a acestora este
disponibilă aici. Printre cele mai populare operații (vă recomand totuși să vă uitați peste lista completă)
sunt următoarele:
Obiecte JavaScript
Un obiect in JavaScript poate fi văzut ca o colecție neordonată de date (valori) ce pot avea tipuri diferite,
dar împreună au o anumită semantică – spre exemplu “datele” despre o persoană și acțiunile întreprinse
de persoana respectivă. Pentru a accesa fiecare dată / valoare din cadrul unui obiect este nevoie și de o
cheie, astfel putem privi un obiect și ca o colecție de perechi (cheie, valoare). Este mai natural să ne
referim la cheile cu care se accesează datele unui obiect cu numele de atribut-ul obiectului sau
proprietatea obiectului, valorile asociate acestora putând fi primitive numerice, boolean-e, string-uri,
dar și referințe la alte obiecte, tablouri sau funcții (acestea două din urmă fiind tot obiecte).
Exemplu:
var person = {
name: 'Chuck Norris',
strength : Infinity,
}
Proprietățile obiectului de mai sus sunt name și strength iar valorile asociate acestora sunt de tip
string, și number. Unui obiect pot să îi fie atribuite proprietăți și mai târziu, astfel putem să-l facem pe
Chuck Norris oricând nemuritor:
person.immortal = true;
Proprietățile unui obiect pot să primească ca și valori funcții, astfel adăugăm metode obiectului
respectiv:
person.kick = function() {
this.opponents = null;
}
In acest moment, obiectul dat ca exemplu, are un nume ( 'Chuck Norris'), putere (Infinity), este
nemuritor (are proprietatea immortal setată la valoarea true) și prezintă o metodă numită kick care
însă nu a fost apelată. În cadrul acestei metode, this (cuvânt rezervat) indică spre obiectul pe care se
va apela funcția, proprietatea opponents adăugându-i-se acestuia (obiectul încă nu are aceasta
proprietate, ea va fi adăugată la apelul metodei).
Dacă Chuck Norris dă cu piciorul, adică dacă se invocă metoda kick pe acest obiect:
person.kick();
obiectul dat ca exemplu va avea o proprietate nouă numită opponents cu valoarea null (Chuck Norris
anihilându-și toți adversarii cum e și normal).
Proprietățile unui obiect pot să fie accesate și folosind o notație de forma (a se observa asemănarea
dintre obiecte și Array-uri):
delete person.immortal
Revenind la tablouri, am specificat anterior că acestea sunt tot obiecte. Valorile memorate în cadrul unui
obiect de tip tablou putând fi accesate prin intermediul indecșilor numerici ( x[0], x[1], x[2]…),
chiar și o expresie de forma x["1"] fiind corectă (nu și una de forma x.1).
Puțin mai târziu în cadrul acestui document vom relua discuția despre obiectele JavaScript după ce
discutăm de scop global.
Instrucțiuni de control
Instrucțiunile de control while, for, if sunt identice cu cele din limbajele C/C++/Java. Insistăm însă pe
o variantă de for care permite iterarea elementelor unui tablou sau a proprietăților unui obiect. Spre
exemplu, pentru a vedea care sunt proprietățile obiectului person declarat mai sus, le putem itera cu (a
se rula codul în consola JavaScript din Developer Tools):
for (i in person)
console.log(i + ' are valoarea ' + person[i]);
Observație: In exemplu de mai sus valorile proprietăților iterate pot fi accesate cu o expresie de forma
person[i] dar nu cu o expresie de forma person.i (aceasta din urmă s-ar referi la o proprietate i pe
care are avea-o obiectul person, proprietate pe care obiectul nu o are).
x = [5, 6, 7];
x[10] = 0;
for (i in x)
console.log('x[' + i + '] = ' + x[i]);
Mai târziu în acest material vom itera proprietățile a două obiecte importante JavaScript: window și
document.
In contextul folosirii dese a unui obiect, se poate folosi instrucțiunea with pentru a simplifica codul și a
nu repeta folosirea numelui obiectului. Spre exemplu, dacă dorim să vedem câți inamici are Chuck Norris
după ce lovește fulgerător, putem folosi:
with(person) {
kick();
console.log(opponents);
}
In secțiunea de față a prezentului material mai insistăm pe folosirea operatorului de comparație ===
(față de folosirea operatorului ==). Ambii operatori se folosesc pentru testarea egalității a două valori,
dar === verifică în plus (pe lângă faptul că cele două expresii sunt evaluate la aceeași valoare) și faptul
că cele două valori sunt de același tip. Un astfel de operator este in general specific limbajelor weakly-
typed (mai este prezent de exemplu în PHP) și nu se regăsește in limbajele strongly-typed. Exemplu:
În exemplele date până în prezent în materialul de față am folosit unele funcții precum alert() sau
obiecte precum document care par predefinite. Acestea nu sunt predefinite, ele de fapt există ca funcții
membre, respectiv date (proprietăți) membre în cadrul unui obiect global numit window care
abstractizează fereastra browser-ului. Nu este greșit de exemplu să folosim expresii de forma
window.alert() sau window.document, dar specificarea explicită a obiectului window este
redundantă.
Un exercițiu interesant este iterarea (folosind forma instrucțiunii for prezentată anterior) datelor și
funcțiilor membre (proprietățile) ale obiectelor window și document. Astfel, pentru a realiza un fel de
introspecție pe obiectul window, puteți încerca în consola JavaScript a browser-ului:
Puteți observa pe obiectul window existența unor date membre/proprietăți precum document sau
outerWidth precum și a unor funcții membre precum alert sau setTimeout. Identic, se poate face
introspecție pe document:
Aruncați in mare o privire peste tot ce „are” documentul ca proprietăți. Exemple de proprietăți ale
obiectului document care ar trebui să vă fie intuitive: title sau location.
Toate variabilele și funcțiile care se declară in interiorul unui tag <script> spunem că sunt declarate in
scopul global (ele sunt accesibile de oriunde din JavaScript). De fapt, ele ajung să fie definite ca date
membre și funcții membre (proprietăți) ale obiectului window. Nu este greșit nici să afirmăm că scopul
global in JavaScript (pentru codul care rulează intr-un browser) este reprezentat de obiectul window.
variabila x și funcția f ajung proprietăți ale obiectului window. Acest lucru se poate verifica ușor făcând
din nou introspecția la proprietățile acestui obiect:
La nivelul scopului global, cuvântul rezervat this va fi referință chiar la obiectul window. Acest lucru se
poate verifica ușor cu:
După cum am văzut la iterarea proprietăților obiectului document, acesta are proprietăți precum
title, location, head, body. DOM-ul (abreviere de la Document Object Model) reprezintă o
structură ierarhică de obiecte construită de către browser pentru a facilita manipularea documentului (a
paginii web) din JavaScript.
Exercițiu: care credeți ca este efectul rulării codului de mai jos în consola JavaScript din Developer Tools?
window.document.body.innerHTML='';
După cum am spus și la începutul acestui material, JavaScript permite (prin intermediul DOM-ului)
modificarea dinamică a conținutului documentului, crearea de noi elemente (tag-uri) in cadrul pagini,
ștergerea unor elemente (tag-uri), modificarea atributelor HTML (adăugarea, ștergerea, schimbarea
valorilor) a unor anumite tag-uri, adăugarea/ștergerea de atribute (proprietăți CSS) sau modificarea
valorilor atributelor CSS existente, sau permite execuția anumitor funcții la apariția anumitor
evenimente. Vom da mai jos câteva exemple pentru toate scenariile înșirate mai sus.
O operație frecventă în JavaScript este obținerea referinței la un element (tag) din pagină pe baza id-ului
său, acest lucru realizându-se cu funcția document.getElementById(). O astfel de operație este
necesară pentru a manipularea din JavaScript a elementului respectiv. Exemplu:
<div id="somediv"></div>
<script>
var mydiv = document.getElementById("somediv");
mydiv.innerHTML = 'Ana are mere';
</script>
Este important în codul de mai sus ca tagul script să succeadă tag-ului div. Dacă tag-ul script ar fi
plasat în secțiunea head a documentului HTML, e posibil ca efectul să nu fie cel așteptat și în consola
JavaScript să aveți un mesaj de eroare legat de faptul ca variabila mydiv este null. Acest lucru se
datorează faptului că in momentul execuției codului JavaScript browserul nu termină de construit DOM-
ul (de parsat și încărcat pagina) și div-ul somediv nu este disponibil încă în DOM.
Proprietatea innerHTML este folosită pe un element container (nu se poate folosi pe elemente/tag-uri
fără corp, doar pe tag-urile cu marcaj de început și sfârșit de tag) pentru a accesa / modifica conținutul
din interiorul tag-ului. Folosind această proprietate se poate seta pe un element container ca și conținut
inclusiv cod HTML. Exemplu:
<div id='somediv'></div>
<script>
var mydiv = document.getElementById('somediv');
mydiv.innerHTML = '<a href="http://www.google.com" id="somelink">Click
here</a>';
var mylink = document.getElementById('somelink');
mylink.style.color = '#00FF00';
mylink.style.backgroundColor = 'red';
</script>
In exemplu de mai sus, in div-ul somediv s-a creat dinamic un tag ancora. Acesta este imediat
disponibil in DOM – am dat și exemple de accesare a acestuia și de modificare a stilurilor CSS folosind
JavaScript (am făcut la cursul de CSS observația că un atribut CSS ce conține liniuța în denumirea sa,
precum text-align se transforma in JavaScript in proprietatea textAlign).
Pe lângă exemplul de mai sus în care am creat un nou element în pagina setând ca valoare pentru
atributul innerHTML a unui container conținut HTML, un element in DOM mai poate fi creat și adăugat
folosind metodele document.createElement() și appendChild(). document.createElement()
primește ca parametru tag-ul (elementul) care se dorește a fi creat, iar appendChild() se apelează pe
un container - spre exemplu container.appendChild(elementnou).
Observați în liniile de cod anterioare folosire metodei setAttribute pe un element HTML pentru
setarea atributelor (și valorilor asociate acestor atribute) elementului. Cele două apeluri setAttribute
de mai sus pot fi rescrise și:
mylink.href = 'http://www.google.com';
mylink.id = 'somelink';
Problemă rezolvată pentru fixarea cunoștintelor: Să se creeze dinamic folosind JavaScript un select cu
100 de option-uri (listă), elementul curent selectat fiind al 50-lea (cel cu valoarea 50). Mai jos dăm trei
variante (oarecum distincte) de rezolvare:
<div id="container">
</div>
<script type="text/javascript">
var container = document.getElementById("container");
var string = '<select name="numar">';
for (var i = 1; i <= 100; i++)
if (i == 50)
string += '<option selected value="' + i + '">' + i +
'</option>';
else
string += '<option value="' + i + '">' + i + '</option>';
string += '</select>';
container.innerHTML = string;
</script>
<div id='container'></div>
<script>
var container = document.getElementById('container');
select = document.createElement('select');
select.setAttribute('name', 'numar');
container.appendChild(select);
for (var i = 1; i <= 100; i++) {
var option = document.createElement('option');
option.setAttribute('value', i);
option.text = i;
select.appendChild(option);
if (i == 50)
option.setAttribute('selected', 'selected');
}
</script>
<div id='container'></div>
<script>
var container = document.getElementById('container');
select = document.createElement('select');
select.name='numar';
container.appendChild(select);
for (var i = 1; i <= 100; i++) {
var option = document.createElement('option');
option.value = i;
option.text = i;
if (i == 50)
option.selected = 'selected';
select.add(option);
oldOption = option;
}
</script>
Pe toate elementele (tag-urile) HTML din cadrul unui document (DOM), API-ul JavaScript prezintă o serie
de funcții și proprietăți și comune tuturor acestor elemente. Câteva exemple intuitive in acest sens sunt
metode precum setAttribute(), click(), remove() sau proprietăți precum tagName și style.
API-ul JavaScript oferă însă pentru o serie de elemente punctuale unele proprietăți și metode specifice
ce pot fi apelate doar pe elementele respective. Un exemplu in acest sens este metoda add() din
exemplul anterior prezentă pe un container de tip select – appendChild() fiind comună tuturor
containerelor. In acest context este important să prezentam un pic modul de manipulare/accesare a
unui tabel (element table) din JavaScript. Dacă myTable reprezintă referința către un element tabel
obținută spre exemplu cu myTable = document.getElementById('someTable'), atunci putem
efectua pe acest tabel următoarele operații
Exemplu. Următoarea secvență de cod populează celulele un tabel HTML cu numere de ordine de la 1 la
n x m unde n reprezintă numărul de linii și m numărul de coloane:
<script>
var myTable = document.getElementById("someTable");
var rows = myTable.rows;
var k = 1;
for (i = 0; i < rows.length; i++) {
var cells = rows[i].cells;
for (j = 0; j < cells.length; j++)
cells[j].innerHTML = k++;
}
</script>
Evenimente
Unul dintre scopurile inițiale ale limbajului JavaScript a fost să faciliteze interacțiunea paginii Web cu
utilizatorul și să permită posibilitatea de a executa anumite secvențe de cod la apariția anumitor
evenimente. Evenimentele JavaScript se pot identifica ca proprietăți (metode membre) pe un element,
metode prefixate de obicei cu prefixul "on" și la a căror apariție se pot executa secvențe de cod sau
asocia funcții care să se execute. Puteți relua exemplele anterioare de iterare a proprietăților /
metodelor prezente pe obiectele window și document pentru a revedea metodele ce pot fi invocate pe
aceste obiecte.
Exemplul 1:
La tratarea unui eveniment, cuvântul rezervat this indică spre obiectul pe care a apărut evenimentul.
Exemplul 2:
Dacă secvența de cod care trebuie executată la apariție evenimentului este mai complexă, aceasta poate
fi plasată și intr-o funcție separată. Exemplul 3:
<script>
function faCeva(element) {
alert("Ati dat click pe un " + element.tagName + " pe care scrie " +
element.innerHTML);
}
</script>
<button onclick='faCeva(this)'>Apasă-l...</button>
În exemplu de mai sus, pentru a avea acces la elementul pe care a apărut evenimentul, referința la acest
element, this, a fost trimisă ca parametrul actual funcției faCeva(). La apelul acesteia, parametrul
formal element va indica spre elementul HTML pe care a apărut evenimentul. Nu este însă obligatorie
trimiterea acestui parametru pentru a avea acces in cadrul funcției care tratează un eveniment la
obiectul care a generat apariția evenimentului. La nivelul unui handler de eveniment (funcție de tratare
a evenimentului), obiectul global window prezintă prin intermediul unei proprietăți (dată membră)
numită event informații despre evenimentul care tocmai se tratează. La rândul său, obiectul event
prezintă o proprietate numită target care indică spre elementul pa care s-a apelat evenimentul. Astfel,
exemplu 3 de mai sus, poate fi rescris in exemplul 4:
<script>
function faCeva() {
var element = window.event.target; // sau simplu event.target
alert("Ati dat click pe un " + element.tagName + " pe care scrie " +
element.innerHTML);
}
</script>
<button onclick='faCeva()'>Apasă-l...</button>
Unui element din cadrul paginii (încărcat în DOM), i poate asocia un eveniment și dinamic (la runtime).
Spre exemplu, odată obținută referința myElem la un element (fie obținută cu
document.getElementById() sau că este vorba de un element proaspăt creat cu
document.createElement()), putem preciza ce se întâmplă la click pe acest element in modul
următor (exemplul 5):
function faCeva() {
alert('S-a dat click!');
}
myElem.onclick = faCeva;
myElem.onclick = function () {
alert('S-a dat click!');
}
In exemplu de mai jos, asociem in acest fel un handler de eveniment pentru tratarea click-urilor pe toate
celulele unui tabel. La click pe o celulă de tabel, dorm să afișam linia și coloana pe care se regăsește acea
celulă. Exemplul 6:
Vă recomand cu căldura sa rulați / testați codul de mai sus. Observați variabilele declarate cu
cuvântul rezervat let. Am amintit anterior în acest material că variabilele pot fi declarate și
folosind let și am promis că revenim asupra folosirii acestuia. Pe scurt, let permite
declararea unor variabile al căror scop este la nivel de bloc. O implementare naivă a codului de mai sus
nu ar fi folosit variabilele auxiliare l și c declarate cu let, iar funcția de tratare a evenimentului click ar
fi arătat astfel:
// incorect !!!
cells[j].onclick = function() {
alert('S-a dat click pe linia ' + i + ' si coloana ' + j);
}
Ce nu este corect în acest caz? Testați exemplul 6, folosind liniile de cod de mai sus pentru tratarea
evenimentului click.
În unele situații este util / se dorește ca pe un element să fie adăugate pentru același eveniment mai
multe funcții de tratare a evenimentul. In acest sens se poate folosi metoda
element.addEventListener. Exemplu:
<button id="myElem">Apasă-l...</button>
<script>
function faCeva() {
alert('Salut');
}
function faAltceva() {
console.log('Salut din nou');
}
Funcția de tratare a unui eveniment dată ca parametru la metoda addEventListener, poate să fie și o
funcție anonimă. Exemplu:
myElem.addEventListener('click', function() {
alert('Salut');
});
Observații:
<button id="myElem">Apasă-l...</button>
<script>
function faCeva() {
alert('Aceasta functie se apeleaza doar la primul click');
event.target.removeEventListener('click', faCeva);
}
La începutul acestui material am făcut o observație deosebit de importantă despre modul de execuție a
codului JavaScript (scrisă cu roșu) și anume că în timp ce browser-ul execută cod JavaScript, acesta nu
„face nimic altceva” – nu (re)randează pagina, nu interacționează cu utilizatorul (nu răspunde la
comenzi/click-uri de mouse etc). Pentru a demonstra și a face mai ușor de înțeles acest lucru, dăm mai
jos următoarea problemă rezolvată:
<body>
Click anywhere for start...
<div style="position: relative; width: 500px; height: 200px; border: 1px
solid black">
<div id="patratel" style="position: absolute; left: 0px; top: 95px; width:
10px; height: 10px; background-color: blue">
</div>
</div>
Coodonate patratel: <span id="label"></span>
</body>
<script>
document.body.onclick = function () {
var patratel = document.getElementById('patratel');
for (i = 0; i <= 500; i+=0.001) {
patratel.style.left = i + 'px';
document.getElementById('label').innerHTML = patratel.style.left;
}
}
</script>
In rezolvarea „naivă” de mai sus, modificăm proprietatea patratel.style.left într-o iterație for.
Dacă rulați acest exemplu, veți observa că pătrățelul nu se deplasează liniar, el fiind afișat de către
browser doar în pozițiile inițială și finală. El nu este randat în nicio poziție intermediară pentru că, codul
JavaScipt nu se termină (se execută dintr-o bucată). Mai mult, încercați după ce ați dat click și ați pornit
mutarea pătrățelului să dați un F12 pentru a deschide Developer Tools. Funcționează?
Varianta corectă de rezolvare presupune deplasarea pătrățelului cu un anumit număr de pixeli mai la
dreapta, după care, peste un anumit număr de milisecunde repetarea acestei operații. Din păcate (sau
din fericire ) JavaScript nu oferă o funcție sleep clasică care să permită „așteptarea” unui număr de
milisecunde (și dacă ar exista o astfel de funcție, cat timp s-ar executa funcția sleep, tot cod JavaScript
s-ar executa, problema nerandării pătrățelului s-ar păstra). In schimb, JavaScript permite prin folosirea
funcției setTimeout (funcție membră pe obiectul window) apelarea unui anumite funcții peste un
număr precizat de milisecunde:
<script>
function salut() {
alert('Salut cu o intarziere de 3 secunde');
}
setTimeout(salut, 3000);
</script>
Observații:
- Aveți grija să dați ca parametru numele funcției - salut în cazul de fată, nu să apelați funcția
salut() – cu paranteze după. O expresie de forma setTimeout(salut(), 3000) este
greșită pentru că duce la execuția instantă a funcției salut, nu la apelarea ei peste 3000 de
milisecunde;
- Dacă funcția care se dorește a fi apelată are parametrii, aceștia se specifică la funcția
setTimeout după numărul de milisecunde, spre exemplu: setTimeout(salut, 3000,
mesaj). Total greșit: setTimeout(salut(mesaj), 3000) pentru că ar duce din nou la
apelul instant al funcției salut;
- Funcția dată ca parametru funcției setTimeout și care trebuie executată peste un anumit
număr de milisecunde, poate fi și o funcție anonimă. Astfel, exemplu de mai sus poate fi scris
mai scurt astfel:
<script>
setTimeout(function () {
alert('Salut cu o intarziere de 3 secunde');
}, 3000);
</script>
Dacă se dorește reapelarea periodică a funcției din 3000 in 3000 de milisecunde, funcția apelată se
poate termina cu un nou setTimeout:
<script>
function salut() {
alert('Salut din 3 in 3 secunde');
setTimeout(salut, 3000);
}
setTimeout(salut, 3000);
</script>
Apelarea automată peste o anumită perioada de timp stabilită cu funcția setTimeout poate fi anulată
prin intermediul funcției clearTimeout. Funcția clearTimeout primește ca parametru o variabilă
întoarsă de setTimout-ul pe care doriți să-l anulați. Exemplu:
Puteți privi variabila t ca pe un fel de identificator de thread – deși exprimarea nu e tocmai corectă.
<script>
setInterval(function() {
alert('Salut din 3 in 3 secunde');
}, 3000);
</script>
Revenind la problema cu pătrățelul, varianta 2 (corectă) a rezolvării este disponibilă mai jos, iar codul
aici.
<body>
Click anywhere for start...
<div style="position: relative; width: 500px; height: 200px; border: 1px
solid black">
<div id="patratel" style="position: absolute; left: 0px; top: 95px; width:
10px; height: 10px; background-color: blue">
</div>
</div>
Coodonate patratel: <span id="label"></span>
</body>
<script>
var i = 0;
document.body.onclick = function () {
var patratel = document.getElementById("patratel");
var t = setInterval(function() {
if (i < 500) {
i+=0.5;
patratel.style.left = i + 'px';
document.getElementById('label').innerHTML = patratel.style.left;
} else
clearInterval(t);
}, 1);
}
</script>
Ce se întâmplă dacă pe varianta corectă dați de mai multe ori click? Puteți explica „fenomenul”?
Uneori se dorește ca execuția unui secvențe de cod JavaScript sau încărcarea unui fișier ce conține cod
JavaScript să se facă ulterior încărcării paginii (în principal pentru a reduce timpul de încărcare și de
randare al acesteia). Un tag script se poate crea dinamic din cod JavaScript la fel ca orice alt element
HTML (precum select-urile și option-urile din exemplele anterioare prezentare în acest material.
Dăm mai jos un exemplu in acest sens.
In fișierul extern.js:
alert('Hello World!');
<body>
<script>
var newScript = document.createElement('script');
newScript.src = 'extern.js';
document.body.appendChild(newScript);
</script>
</body>
1. Se dorește executarea unei funcții faCeva la încărcarea paginii (documentului HTML) și în acest sens
se apelează funcția faCeva pe evenimentul onload al elementului body. Exemplu:
<script>
function faCeva() {
document.getElementById("demo").innerHTML = "Hello World!";
}
</script>
<body onload="faCeva()">
<div id="demo">
</div>
</body>
Deși e posibil ca acest exemplu să funcționeze, există pericolul ca la momentul execuției funcției faCeva
(adică la apariția evenimentului onload pe body = încărcarea body-ului in DOM), div-ul cu id-ul demo
să nu fie încărcat încă în DOM. Acest lucru face ca document.getElementById("demo") să
returneze null. Soluția corectă este apelarea funcției spre execuție într-un tag <script> plasat la
sfârșitul documentului:
<script>
function faCeva() {
document.getElementById("demo").innerHTML = "Hello World!";
}
</script>
<body>
<div id="demo">
</div>
</body>
<script>
faCeva();
</script>
Vom vedea cursul următor că jQuery oferă o modalitate mult mai elegantă de a executa ceva la
încărcarea documentului.
Uneori este necesară construirea dinamică a unui element imagine și încărcarea dinamică a acestuia în
DOM. Problemele apar când se dorește accesarea dimensiunilor imaginii proaspăt create. În exemplul
de mai jos, se construiește dinamic un element imagine, se stabilește URL-ul (fișierul sursă) pentru acest
element și se dorește afișare imaginii la 50% din rezoluția imaginii originale (înjumătățim lățimea
imaginii, înălțimea se va autoscala automat):
<body></body>
<script>
var img = new Image();
// sau var img = document.createElement('img');
img.src = "poza.jpg";
img.width = img.width / 2;
// dorim sa afisam poza la jumatatea rezolutiei acesteia
document.body.appendChild(img);
</script>
Exemplul de mai sus (disponibil on-line aici) este posibil să nu ofere rezultatul dorit, pentru că la
momentul folosirii expresiei img.width ca și right value, imaginea nu se termină de încărcat iar lățimea
acesteia este necunoscută (img.width va avea valoarea 0), img.width folosit că și left value primind
valoare 0 (fapt ce va duce la neafișarea imaginii nici când aceasta este încărcată complet în browser).
Dacă exemplu vă funcționează puteți să-l încercați cu o imagine de rezoluție mai mare sau să dați un
refresh (CTRL-F5). Varianta corectă disponibilă aici presupune scalarea imaginii doar când aceasta se
încarcă in DOM (browser-ul a terminat încărcarea acesteia) și se cunoaște lățimea acesteia:
<body></body>
<script>
var img = new Image();
// sau var img = document.createElement('img');
img.src = "poza.jpg";
img.onload = function() {
img.width = img.width / 2;
}
document.body.appendChild(img);
</script>
Redăm mai jos câteva exemple pentru a face mai ușor de înțeles atât unele concepte OOP din JavaScript
cât și unele aspecte „de finețe” ale acestui limbaj.
Fie secvența JavaScript de mai jos care dorește să calculeze media notelor unui student:
<script>
function Student(name, grades) {
this.name = name;
this.grades = grades;
}
function displayStudentAverage() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
Am amintit anterior în acest material că funcțiile și variabilele declarate în scopul global ajung
proprietăți membre ale obiectului window. Ambele funcții de mai sus fiind definite în scopul global,
ajung funcții membre ale acestui obiect. După apelul funcției Student, obiectul window va deține două
proprietăți noi, name și grades care memorează numele și notele studentului (pentru a vă convinge de
acest lucru puteți itera proprietățile obiectului window, veți observa pe acest obiect patru proprietăți
noi: Student, displayStudentAverage, name și grades). Funcția displayStudentAverage() în
exemplu de cod se mai sus se apelează tot pe obiectul window, calculând și afișând media pentru array-
ul de note memorate pe obiectul window.
Observație: în exemplul de mai sus, this în ambele funcții va fi referință spre obiectul window pe care
se apelează cele doua funcții. this.name și this.grades vor indica spre proprietăți ale obiectului
window. Variabilele sum și average sunt variabile locale în cadrul funcției displayStudentAverage,
ele neaparținând obiectului window.
Exemplul 2 (cod disponibil aici)
Ne dorim evident să calculăm și afișăm media pentru mai mulți studenți. Funcția Student poate fi
folosită și ca și constructor pentru a construi un obiect de tipul Student, precum în exemplul de mai
jos. s1 și s2 vor fi două obiecte noi, ambele proprietăți ale obiectului window (sunt declarate cu var în
scopul global), fiecare student având propriul nume și tablou cu note. In cadrul funcției Student, this
de această dată va indica spre obiectul nou construit. Pe exemplul anterior (exemplul 1), funcția
displayStudentAverage nu poate fi apelată pe un student, displayStudentAverage fiind funcție
membră (proprietate) a obiectului window, nu a unui obiect Student. Pentru a putea afișa media unui
student, în cadrul funcției constructor Student putem adăuga o proprietate membră funcție numită
show care să indice spre funcția de afișare a mediei displayStudentAverage.
<script>
function Student(name, grades) {
this.name = name;
this.grades = grades;
this.show = displayStudentAverage;
}
function displayStudentAverage() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
<script>
function Student(name, grades) {
this.name = name;
this.grades = grades;
this.show = displayStudentAverage;
function displayStudentAverage() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
}
În acest exemplu, oriunde în cadrul funcției Student (puteți sa va gândiți la funcția Student ca la o
clasa, începe să semene cu o clasa din C++/Java...), funcția displayStudentAverage se comportă ca o
funcție privată, în timp ce show se comportă ca o funcție publică (putând fi apelată din exterior pe un
obiect de tipul Student). De altfel, toate proprietățile membre (indiferent că sunt date membre sau
funcții membre) stocate cu "this." în cadrul constructorului sunt publice (pot fi accesate din exterior
pe un obiect de tipul Student) - a se vedea ultimul alert din exemplul de mai sus.
Funcția care afișează media unui student poate fi declarată și ca funcție anonimă:
<script>
function Student(name, grades) {
this.name = name;
this.grades = grades;
this.show = function() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
}
Studentul s1 din exemplul anterior ar mai putea fi definit și in modul următor, însă o astfel de definire
nu este utilă în momentul în care dorim să declaram mai multe obiecte de același tip:
<script>
var s1 = {
name: "Pop Ionel",
grades: [6, 8, 10],
show: function() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
};
s1.show();
</script>
<script>
function Student(name, grades) {
var _average;
var _name = name;
var _grades = grades;
this.show = function() {
computeAverage();
alert(_name + " - media " + _average);
}
function computeAverage() {
var sum = 0;
for (var i = 0; i < _grades.length; i++)
sum += _grades[i];
_average = sum / _grades.length;
}
}
Pentru a simplifica codul, în exemplul anterior putem renunța la variabilele locale _name și _grades și
să folosim în interiorul "clasei" direct parametrii formali name și grades ai funcției Student:
<script>
function Student(name, grades) {
var _average;
this.show = function() {
computeAverage();
alert(name + " - media " + _average);
}
function computeAverage() {
var sum = 0;
for (var i = 0; i < grades.length; i++)
sum += grades[i];
_average = sum / grades.length;
}
}
Funcționalitatea unui obiect Student poate fi extinsă cu noi metode. Astfel, la fel cum l-am înzestrat pe
Chuck Norris cu metoda kick într-un exemplu anterior, putem să-l înzestrăm pe studentul s1 cu o
metoda hasToRepeatAnExam care va returna true daca acest student are cel puțin o materie restantă
și false în caz contrar:
<script>
function Student(name, grades) {
this.name = name;
this.grades = grades;
this.show = function() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
}
s1.show();
s2.show();
s1.hasToRepeatAnExam = function() {
for (var i = 0; i < this.grades.length; i++)
if (this.grades[i] < 5)
return true;
return false;
}
</script>
Observații importante:
JavaScript prototype
Exemplul 9 (cod disponibil aici)
Evident este de dorit să extindem uneori funcționalitatea tuturor obiectelor de același tip. Adică, să
înzestrăm toate obiectele de tip Student (gata instanțiate sau instanțiate pe viitor) cu o metodă
hasToRepeatAnExam care să poate fi apelata pe aceste obiecte. Acest lucru se poate face în JavaScript
prin intermediul unei proprietăți speciale a funcției constructor Student denumita prototype.
<script>
function Student(name, grades) {
this.name = name;
this.grades = grades;
this.show = function() {
var sum = 0;
for (var i = 0; i < this.grades.length; i++)
sum += this.grades[i];
var average = sum / this.grades.length;
alert(this.name + " - media " + average);
}
}
s1.show();
s2.show();
Student.prototype.hasToRepeatAnExam = function() {
for (var i = 0; i < this.grades.length; i++)
if (this.grades[i] < 5)
return true;
return false;
}
Folosind proprietatea prototype poate fi extinsă inclusiv funcționalitatea unor tipuri predefinite din
JavaScript. Spre exemplu, am enumerat anterior diverse metode care pot fi apelate pe un Array, dar
din păcate pe un astfel de obiect lipsește o metoda shuffle care ar fi utilă când se dorește
"amestecarea" (randomizarea ordinii) elementelor din tablou. Folosind prototype este ușor de
implementat o astfel de metodă ce poate fi ulterior apelată pe orice Array. Codul sursa de mai jos este
disponibil on-line si aici:
<script>
Array.prototype.shuffle = function() {
this.sort(function (x, y) {
return (Math.random() * 2 - 1);
});
}
Metoda shuffle apelează metoda sort pe tablou ce trebuie sortat (this), sort având ca parametru
o funcție anonima custom de comparație a două elemente ale tabloului (această funcție returnează
aleator dacă doua elemente x si y comparate trebuie interschimbate sau nu).
Cu Developer Tools pornit (F12), puteți da refresh pe pagina (F5). Observați valorile din tabloul x afișate
la consolă într-o ordine aleatoare în urma execuției metodei shuffle pe acest tablou.
Este posibil să fi promis pe parcursul materialului că vom detalia mai târziu anumite lucruri și să îmi fi
scăpat să revin la ele. Sunt deschis la orice sugestii de îmbunătățire a acestui material și observații
privind eventuale scăpări (acord bonusuri recompensă ). Mulțumesc.