Documente Academic
Documente Profesional
Documente Cultură
JavaScript
pentru începători
absoluți
Învățați să scrieți cod JavaScript
eficient de la zero
Terry McNavage
www.allitebooks.com
www.allitebooks.com
JavaScript
pentru începători
absolut
■■■
Terry McNavage
i
www.allitebooks.com
JavaScript pentru începători absolut
În această carte pot apărea nume, logo-uri și imagini cu marcă înregistrată. În loc să folosim un simbol
de marcă comercială la fiecare apariție a unui nume, logo sau imagine de marcă comercială, noi
folosim numele, logo-urile și imaginile doar în mod editorial și în beneficiul proprietarului mărcii
comerciale, fără intenția de a încălca marca comercială.
Informațiile din această carte sunt distribuite "ca atare", fără nicio garanție. Deși au fost luate toate
măsurile de precauție în pregătirea acestei lucrări, nici autorul (autorii), nici Apress nu vor avea nicio
răspundere față de nicio persoană sau entitate în ceea ce privește orice pierdere sau prejudiciu cauzat
sau presupus a fi cauzat direct sau indirect de informațiile conținute în această lucrare.
ii
www.allitebooks.com
Pentru Mica Floare, Sfânta Tereza de Lisieux, pentru că mi-a trimis acest trandafir.
iii
www.allitebooks.com
Cuprins dintr-o privire
■ Cuprins.........................................................................................................v
■ Despre autor.................................................................................................xiii
■ Despre evaluatorii tehnici...............................................................................xiv
■ Recunoștințe..............................................................................................xv
■ Prefață .......................................................................................................xvi
■ Capitolul 1: Reprezentarea datelor cu valori........................................................1
■ Capitolul 2: Conversia de tip............................................................................25
■ Capitolul 3: Operatori .....................................................................................57
■ Capitolul 4: Controlul fluxului ..........................................................................97
■ Capitolul 5: Moștenirea membrilor .................................................................145
■ Capitolul 6: Funcții și array-uri.......................................................................181
■ Capitolul 7: Traversarea și modificarea arborelui DOM .....................................255
■ Capitolul 8: Scripting CSS .............................................................................307
■ Capitolul 9: Ascultarea evenimentelor ............................................................347
■ Capitolul 10: Scripting BOM ..........................................................................399
■ Index .........................................................................................................461
iv
www.allitebooks.com
Cuprins
v
www.allitebooks.com
■ CUPRINS
Rezumat......................................................................................................56
■ Capitolul 3: Operatori .....................................................................................57
Introducerea precedenței și asociativității operatorilor ............................................57
Utilizarea operatorilor JavaScript ........................................................................60
Combinarea operațiilor matematice și de atribuire ........................................................................61
Creșterea sau descreșterea valorilor ...........................................................................................66
Testarea pentru egalitate ...........................................................................................................68
Testarea inegalității...................................................................................................................70
Compararea obiectelor, a tablourilor și a funcțiilor ............................................................................72
Determinarea dacă un număr sau un șir de caractere este mai mare decât altul ....................................74
Determinarea dacă un număr sau un șir de caractere este mai mic decât altul ......................................77
Mai mare sau egal cu, mai mic sau egal cu .....................................................................................78
Crearea unor comparații mai complexe ..........................................................................................81
Spunând sau cu || .....................................................................................................................83
Spunând "și" cu && .................................................................................................................84
vi
www.allitebooks.com
■ CUPRINS
Rezumat......................................................................................................95
■ Capitolul 4: Controlul fluxului ..........................................................................97
Scrierea unei condiții if ...................................................................................98
Adăugarea unei clauze else........................................................................................................100
A împacheta sau a nu împacheta ..............................................................................................101
Codificarea mai multor căi de acces cu expresia else if ...............................................................102
Controlul fluxului cu ajutorul expresiilor condiționate ...................................................................105
Moștenirea prototipală...................................................................................171
Clonarea membrilor.......................................................................................174
Mixins........................................................................................................176
Rezumat....................................................................................................179
■ Capitolul 6: Funcții și array-uri.......................................................................181
De ce să folosiți funcții? ....................................................................................181
Funcțiile sunt valori........................................................................................183
Membrii funcției ............................................................................................184
Încărcare anticipată condiționată .......................................................................185
Scrierea Object.defineProperty() .............................................................................................186
Scrierea Object.defineProperties()...........................................................................................187
Scrierea Object.create().........................................................................................................188
Utilizarea noilor funcții .............................................................................................................189
www.allitebooks.com
■ CUPRINS
Rezumat....................................................................................................253
■ Capitolul 7: Traversarea și modificarea arborelui DOM .....................................255
Copac DOM ..................................................................................................255
Este fiecare nod la fel? ..............................................................................................................256
Interfețele sunt denumite în mod sensibil ...................................................................................257
Interogarea arborelui DOM..........................................................................................................257
Același jargon ca pentru un arbore genealogic ...........................................................................260
Traversarea arborelui DOM.........................................................................................................260
Coborârea cu childNodes .........................................................................................................260
Ascendent cu parentNode........................................................................................................262
Muddying apele cu spațiu alb ...................................................................................................263
Codificarea stilului Cascade........................................................................................................264
Deplasarea laterală ................................................................................................................268
Conversia unei liste de noduri într-o matrice ..................................................................................271
Conversia unei liste de noduri într-o matrice pentru Internet Explorer............................................273
Traversarea DOM fără childNodes.............................................................................................275
Găsirea unui element după ID...................................................................................................277
Găsirea elementelor după numele lor de etichetă.............................................................................278
Găsirea elementelor după clasă ................................................................................................279
ix
■ CUPRINS
Creareadeelementecuofuncțieajutătoare.11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111294
Reordonarea listelor imbricate. 296
Unde au dispărut nodurile de formatare a textului? . 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111302
Rezumat. 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111304
■ Capitolul 8: Scripting CSS.............................................................................307
Interfețe DOM pentru lucrul cu CSS .11111111111111111111111111111111111111111111111111111111111111111111111307
Clarificarea unui jargon CSS .11111111111111111111111111111111111111111111111111111111111111111111111111111111111111308Cum
reprezintăJavaScriptoregulă?.1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111308Altedouăblocuride
declarați.1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111310
x
■ CUPRINS
Animarea cu cronometre.................................................................................407
Pregătirea derulatorilor ............................................................................................................407
Adăugarea ascultătorului de evenimente de presă ..........................................................................410
Scrierea funcției de animație ....................................................................................................411
Utilizarea galeriei ....................................................................................................................413
Scrierea paginilor dinamice folosind Ajax ...........................................................421
Testarea XMLHttpRequest din sistemul de fișiere local....................................................................422
Crearea de ramuri de arbore cu createElem().............................................................................422
Solicitarea asincronă a datelor ....................................................................................................425
Parsarea unui răspuns HTML......................................................................................................427
Parsarea unui răspuns XML........................................................................................................431
Parsarea XML simplu ..............................................................................................................435
Parsarea JSON .......................................................................................................................439
Cedarea cu cronometre ..................................................................................449
Conversia declarațiilor de funcții în expresii........................................................450
Rezumat....................................................................................................458
■ Index .........................................................................................................461
xii
Despre autor
■ Terry McNavage, www.popwebdesign.com, a codat manual JavaScript timp de 12 ani. Pe lângă faptul că
este un expert în JavaScript, are experiență în design creativ, XHTML, CSS, PHP, Perl și MySQL. Terry
este și un alergător de elită. În ultimii 14 ani, a alergat 160 de kilometri sau mai mult pe săptămână pe
terenul accidentat din Pittsburgh. Este, de asemenea, un mic gurmand. Deși Pirates au avut 18 sezoane
pierdute la rând, Terry speră că în 2011 vor ridica Jolly Roger mai des decât steagul alb.
xiii
■ CUPRINS
■ Rob Drimmie este norocos. Are o soție extraordinară, doi copii minunați și o
tastatură nouă. Impulsurile creative ale lui Rob tind să se manifeste sub forma unor
aplicații web și preferă ca acestea să fie alimentate cu pho și hamburgeri - adică
impulsurile creative.
■ Tom Barker este inginer de software, arhitect de soluții și manager tehnic, cu peste
un deceniu de experiență în lucrul cu ActionScript, JavaScript, Perl, PHP și Microsoft
.NET Framework. În prezent, este manager de dezvoltare web la Comcast Interactive
Media, unde conduce grupul de dezvoltatori responsabili pentru www.comcast.net și
www.xfinity.com. Este, de asemenea, profesor adjunct la Universitatea din Philadelphia,
unde predă cursuri de dezvoltare web pentru studenți și absolvenți din 2003, precum și
un colaborator regulat la www.insideRIA.com. Atunci când nu lucrează, nu predă sau nu
scrie, lui Tom îi place să petreacă timp cu familia, să citească și să joace jocuri video
până foarte devreme dimineața.
xiv
Recunoștințe
Doresc să mulțumesc familiei mele - mama, tata, John și Ryan - pentru dragostea și sprijinul lor. De
asemenea, doresc să le mulțumesc tuturor celor de la Apress, în special lui Ben Renow-Clarke, Matthew
Moodie, Kristian Besley, Dominic Shakeshaft și Mary Tobin, pentru sârguința, răbdarea și încurajarea
lor.
-Terry McNavage
xv
■ PREFAȚĂ
Prefață
În adaptarea cinematografică din 2005 a Ghidului autostopistului galactic de Douglas Adams, extratereștrii
demolează Pământul pentru a face loc unei autostrăzi hiperspațiale. Dispariția noastră ar fi putut fi
evitată în măsura în care propunerea de demolare a fost depusă de ceva timp la birourile locale de
planificare din întreaga lume. Cu toate acestea, nimeni nu s-a plâns în timpul perioadei de comentarii
publice.
Ca și în cazul propunerilor de construcții, nimeni nu se obosește să citească prefața unei cărți de programare.
În mod normal, acest lucru este în mare parte inofensiv, dar nu și în cazul acestei cărți. Deși nu veți fi
vaporizat în praf stelar pentru că ați sărit la capitolul 1 sau mai târziu, veți fi nedumerit pentru că nu ați
descărcat și nu v-ați familiarizat cu Firebug, instrumentul nostru pentru învățarea JavaScript.
JavaScript este un limbaj de programare ușor de utilizat de începători, disponibil în browsere
precum Internet Explorer, Firefox, Safari, Opera și Chrome. Aceste browsere conțin un interpretor
JavaScript care analizează și execută programele JavaScript, pe care le scrieți în text simplu cu un
editor de text. Așadar, puteți folosi același editor de text cu care vă codificați XHTML și CSS.
JavaScript își derivă sintaxa, adică gramatica, din standardul ECMAScript, iar caracteristicile
sale pentru manipularea XHTML, CSS și HTTP din standardul DOM. În mod obișnuit, interpretorii
JavaScript implementează ECMAScript și DOM în biblioteci separate. Așadar, așa cum creierul
dumneavoastră are lobi stângi și drepți, creierul JavaScript al unui browser are lobi ECMAScript și
DOM.
În primele șase capitole, vom dialoga cu lobul ECMAScript. Apoi vom dialoga cu lobul DOM pentru
câteva capitole. Cred că se poate spune că vom culege creierul unui JavaScript, lob pe rând - ECMAScript
și apoi DOM, cu Firebug. În cele din urmă, în ultimele două capitole, vom codifica manual un program
JavaScript uber-cool cu editorii noștri de text preferați. Dar nu vom reuși niciodată să parcurgem capitolele
1-8 fără Firebug. Așadar, primul nostru ordin de zi va fi să vă rugăm să descărcați și să vă familiarizați cu
Firebug, un add-on gratuit pentru Firefox pentru Windows, Mac sau Linux.
Evident, înainte de a instala un add-on Firefox precum Firebug, trebuie să aveți Firefox. Rețineți că
Firefox este un browser web gratuit pentru Windows, Mac OS X sau Linux. Pentru a descărca Firefox,
accesați www.mozilla.com și faceți clic pe butonul Download Firefox - Free (Descărcați Firefox - Gratuit),
așa cum este afișat în figura 1. Urmați apoi instrucțiunile expertului pentru a instala Firefox pe computerul
dumneavoastră.
Deschideți Firefox, apoi descărcați add-on-ul Firebug de pe www.getfirebug.com. Pur și simplu faceți
clic pe butonul Install Firebug for Firefox din colțul din dreapta sus, așa cum se arată în figura 2. Urmați
apoi asistentul, acordând permisiunea de a instala add-on-ul dacă Firefox vă solicită acest lucru.
xvi
Figura 1. Descărcarea Firefox pentru Windows, Mac OS X sau Linux
Deschiderea Firebug
Încărcați firebug.html în Firefox, apoi apăsați F12 pentru a deschide Firebug, ca în figura 3. Rețineți
că apăsarea F12 face și invers. Cu alte cuvinte, apăsarea F12 comută Firebug din închis în deschis sau
din deschis în închis. Rețineți că, dacă F12 este o comandă rapidă pentru altceva pe computerul
dumneavoastră, puteți deschide Firebug alegând Tools ⮞ Firebug ⮞ Open Firebug în bara de meniu a
Firefox, așa cum este ilustrat în figura 4.
Figura 4. Deschiderea manuală a Firebug dacă F12 este o comandă rapidă pentru altceva pe computerul dvs.
xviii
www.allitebooks.com
■ PREFAȚĂ
Activarea Firebug
Prima dată când deschideți Firebug, este posibil să trebuiască să îl activați alegând Enabled (Activat) din
meniul Console (Consolă), așa cum se arată în figura 5.
Linia de comandă
Firebug are o linie de comandă pentru a rula o singură linie de JavaScript cu. Aceasta se execută de-a
lungul părții de jos a Firebug și este precedată de >>>. Tastați următorul exemplu pe linia de comandă, ca
în figura 6:
alert("Don't Panic");
Acum apăsați Return pe tastatură pentru ca JavaScript să ruleze eșantionul. După cum arată figura 7,
acest lucru îi spune lui Firefox să deschidă o casetă de dialog de alertă.
Figura 7. Apăsarea tastaturii "Return" de pe tastatură îi spune lui Firefox să deschidă o casetă de dialog de alertă.
Editor de comenzi
Aproape toate mostrele de JavaScript pe care le vom rula în Firebug au mai mult de o linie de cod. Așadar,
linia de comandă nu va fi suficientă. În schimb, vom comuta consola de la linia de comandă la editorul de
comenzi, făcând clic pe pictograma cu săgeată orientată în sus din colțul din dreapta jos al Firebug. După
cum arată figura 8, acest lucru împarte Firebug în două panouri. Cel din dreapta este editorul de comenzi.
Acesta este cel în care veți tasta toate exemplele de cod din această carte.
Rețineți că în partea de jos a editorului de comenzi există trei opțiuni de meniu, Run, Clear și
Copy. Dacă faceți clic pe Run (Executare), veți executa orice cod pe care l-ați tastat în editorul de
comenzi. Rețineți că prescurtarea de la tastatură pentru a face clic pe Run este Ctrl+Return
(Comandă+Return). Altfel spus, apăsând Return (Întoarcere) executați exemplul dvs. în linia de
comandă, dar nu și în editorul de comenzi. Dacă ar fi fost altfel și Return ar fi servit la rularea codului
în editorul de comenzi, nu ați fi putut introduce mai mult de o linie de cod. Cu alte cuvinte, editorul de
comenzi ar rula prima linie de cod pe care ați tastat-o, deoarece ați apăsa Return după ce ați introdus-o;
nu ați avea niciodată șansa de a introduce o a doua linie!
Celelalte două, Clear și Copy, sunt denumite pe bună dreptate. Dacă faceți clic pe Clear (Șterge),
veți șterge orice cod din editorul de comenzi, în timp ce dacă faceți clic pe Copy (Copiază), veți copia
orice cod din editorul de comenzi în clipboard. Rețineți că, pentru a șterge panoul din stânga al
Firebug, trebuie să faceți clic pe Clear (Șterge) din meniul acestuia. Așadar, există o opțiune Clear
(Șterge) atât în panoul din stânga, cât și în cel din dreapta. Deseori în această carte voi spune "double-
clear Firebug", ceea ce reprezintă indiciul pentru a face clic pe Clear în ambele meniuri.
xx
■ PREFAȚĂ
Figura 8. Editorul de comenzi are un meniu separat cu opțiunile Run, Clear și Copy.
OK, tastați exemplul anterior în editorul de comenzi, apoi faceți clic pe Run sau apăsați
Ctrl+Return (Command+Return) pentru ca JavaScript să îl execute:
alert("Don't Panic");
După cum arată Figura 9, Firefox va deschide o casetă de dialog de alertă, la fel ca înainte.
Figura 9. Dacă faceți clic pe Run (Execută), Firefox va deschide o casetă de dialog de alertă.
Un lucru de reținut este că editorul de comenzi și linia de comandă se află în fila Console din
Firebug. Prin urmare, dacă treceți din greșeală la fila HTML, CSS, Script, DOM sau Net, editorul de
comenzi va dispărea. Așadar, va trebui să faceți clic pe fila Console din colțul din stânga sus pentru a
face comanda
xxi
■ PREFAȚĂ
editorul reapare. Rețineți că prescurtarea de la tastatură pentru trecerea la fila Console este Ctrl+Maj+L
(Command+Maj+L). Tabelul 1 enumeră comenzi rapide de la tastatură vitale pentru Firebug.
Dacă sunteți un dactilograf falibil, inevitabil veți scrie greșit un eșantion de cod. În consecință, atunci
când faceți clic pe Run, JavaScript va imprima o eroare în panoul din stânga al Firebug. Acestea sunt
pur și simplu modalitatea prin care JavaScript vă numește prostănac.
Cele mai frecvente sunt erorile de sintaxă și de referință. JavaScript le numește SyntaxError și,
respectiv, ReferenceError. Așadar, haideți să o dăm în bară în ambele sensuri acum pentru a vă scăpa de
erorile de schneid. În Firebug, scrieți greșit alert ca alrt pentru a face o eroare de referință, adică ați
scris greșit numele a ceva:
alrt("Don't Panic");
După cum arată figura 10, JavaScript tipărește un ReferenceError care conține mesajul "alrt is not
defined":
OK, reparați greșeala de tipar, schimbând alrt în alert, apoi ștergeți parantezele de închidere astfel:
alert("Don't Panic";
Acum faceți clic pe Run (Executare). După cum arată figura 11, JavaScript tipărește o eroare de
sintaxă (SyntaxError) care conține mesajul "missing ) after argument list". Rețineți că o eroare de
sintaxă în programare este ca o eroare gramaticală în scris.
Figura 11. Oops-JavaScript returnează un SyntaxError care spune "lipsă ) după lista de argumente".
Nu vă panicați dacă primiți o eroare. Probabil înseamnă doar că trebuie să corectați o greșeală de scriere
sau două.
Acum că ați instalat și v-ați familiarizat cu Firebug, să începem să explorăm ECMAScript!
xxiii
CHAPTER 1
■■■
Când intri în casa copilăriei mele din Pittsburgh, este evident că acolo locuiește un tip cu o minte vie.
Fotografii din călătoriile efectuate în treizeci și una de țări de pe șase continente sunt aliniate pe
pereți. Printre acestea se află artă aborigenă și aleută, stampe de Klimt și Degas, tapiserii din Egipt și
Peru și sculpturi grecești. Lucrări literare notabile umplu biblioteca.
Deși conversațiile cu tatăl meu sunt interesante, ele tind să fie presărate cu ceea ce mama mea ar
numi "comentariul de nicăieri", un fragment neprecizat din orice la care se gândește. De exemplu, am
fost acolo pentru un meci al echipei Steelers într-o zi umedă de noiembrie. Cred că jucau împotriva celor
de la Ravens, rivalii lor de sânge. Așadar, măcelul a fost destul de medieval. În plus, Heinz Field era un
dezastru. Semăna mai degrabă cu o pășune noroioasă pentru vaci decât cu un teren de fotbal.
La o a treia și lungă, cu Steelers aproape în raza de acțiune a golului, Roethlisberger s-a retras
pentru a pasa. Dar Hines Ward, destinatarul său, a alunecat și a căzut pe un model de sincronizare,
zăcând cu fața în jos în noroi. Astfel, mingea a trecut peste primul marker, fiind incompletă.
În timp ce Steelers se pregăteau să lovească, probabil că am murmurat ceva neimprimabil. Tata, pe
de altă parte, s-a uitat la mine pe deasupra ochelarilor de citit și m-a întrebat: "Știai că francezii ar fi
putut pierde în fața englezilor la Agincourt din cauza adâncimii noroiului?". Deși nu am spus: "Nu, și
de ce îmi spui asta?". Cu siguranță mă gândeam la asta.
Dacă nu sunteți familiarizați cu JavaScript și programarea, unele dintre lucrurile pe care le spun în
primele capitole s-ar putea să vă deruteze, așa cum m-a derutat pe mine întrebarea lui tata. Să știți doar
că, deși am codat manual JavaScript timp de 12 ani, nu am uitat cât de greu poate fi la început. Așadar,
această carte este scrisă în stil conversațional, acoperind doar lucrurile care contează.
Este un fel de noroi până la genunchi pe un câmp proaspăt arat și îmbibat de ploaie care
mărginește pădurea de la Agincourt la 25 octombrie 1415. Acesta s-a dovedit a fi foarte obositor pentru
cavalerii francezi, care au trebuit să se plimbe prin el purtând aproximativ 50-60 de kilograme de
armură completă. Cei care au căzut mai târziu în noroiul adânc în timpul mêléei au avut dificultăți în a-
și recăpăta picioarele, rămânând astfel ținte pentru arcașii englezi. Unii cavaleri francezi călcați în
picioare s-au înecat chiar și în armura lor. În câteva ore, armata franceză a fost zdrobită de o armată
engleză de o cincime din mărimea sa. Istoricii estimează că francezii au murit 10.000 de oameni, față de
112 pentru englezi, atribuind măcelul terenului noroios.
Tata mi-a povestit aceste detalii în timpul cinei de după meci, menționând că pregătea o prelegere
despre Henry V, o piesă de Shakespeare care prezintă bătălia de la Agincourt, pentru un curs pe care îl
ținea la Universitatea Penn State. Așadar, comentariul de nicăieri a venit și el de undeva!
Așa că, rezistați în primele ore de mers cât timp noroiul este adânc. Lucrurile se vor aranja pentru
tine mai târziu în carte, așa cum au făcut-o și pentru mine mai târziu în cursul zilei.
1
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Cel mai simplu mod de a crea un șir de caractere, un număr, un boolean sau o valoare de obiect în
JavaScript este să o introduceți literalmente în script. Astfel, se creează o valoare literală sau, mai
simplu, un literal.
Interpretările JavaScript pentru Firefox și alte browsere returnează o valoare de șir de caractere
între ghilimele duble. Așadar, în această carte vom folosi ghilimele duble. Dar nu contează. Singurul
lucru pe care l-aș spune este să alegeți un stil sau altul și să rămâneți la el.
Rețineți că șirul anterior este urmat de un punct și virgulă. Fiecare instrucțiune, care este pur și
simplu ceva ce îi spuneți lui JavaScript să facă, se termină cu un punct și virgulă. Simpla noastră
instrucțiune prezentată mai devreme îi spune lui JavaScript să creeze un șir literal în memorie. Vom
explora mai pe larg declarațiile în Capitolul 4.
Comentariul Codului
La fel ca CSS sau XHTML, JavaScript vă permite să comentați codul și să îl formatați cu spații albe.
Comentariile pe o singură linie încep cu un //. JavaScript nu ia în considerare tot ceea ce urmează după
// până la sfârșitul liniei. În această carte, exemplele de cod pe care vreau să le introduceți și să le
executați sunt urmate de un comentariu care enumeră valoarea de returnare pe care JavaScript o va
imprima în Firebug. Așadar, pentru a vă anunța că JavaScript va reda în ecou șirurile de caractere literale,
aș scrie următoarele:
"Ben & Jerry's";
// "Ben & Jerry's" "Chocolate
Fudge Brownie";
// "Chocolate Fudge Brownie"
2
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Rețineți că "Ben & Jerry's" + " " + "Chocolate Fudge Brownie" + " este înghețata mea preferată."
se numește expresie pentru o valoare. În JavaScript, acestea sunt orice fraze de cod care creează o
valoare. Vă puteți gândi la o expresie ca la o rețetă pentru o valoare. Vom explora această analogie în
Capitolul 3.
Math.round((6 * 14 + 21) / 7 * 365 * 100 / (4 * 260)) + " halbe de Chocolate Fudge Brownie";
// "526 de litri de ciocolată cu ciocolată și c a r a m e l "
Cred că deocamdată voi rămâne la o dietă organică, cu alimente integrale. Dar, dacă la 90 de ani voi
mai alerga, poate voi încerca și asta!
4
www.allitebooks.com
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
■ Notă Caracteristica de conversie a tipului de valoare din JavaScript este tratată mai pe larg în capitolul 2.
5
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
■ Notă Operatorii de comparare, cum ar fi ===, returnează toți booleeni. În plus, JavaScript poate converti
orice șir de caractere, număr, obiect, null sau valoare nedefinită în boolean. Vom explora conversia tipului de
valoare în capitolul 2. Din aceste motive, booleenii sunt esențiali pentru controlul fluxului, lucru pe care îl vom
explora în capitolul 4.
6
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Motivul pentru care acest lucru funcționează este că = are o precedență foarte mică în comparație cu
* și +. În capitolul 3, vom explora mai pe larg precedența, care determină ordinea ierarhică a operatorilor.
Până la sfârșitul acestei cărți, veți ști ce îi spun toate aceste cuvinte cheie lui JavaScript să facă.
Astfel, până atunci, va fi evident că nu trebuie să numiți o variabilă cu un cuvânt cheie.
Pe de altă parte, versiunile viitoare ale ECMAScript pot adăuga următoarele cuvinte cheie. Acestea tot
nu vor însemna nimic pentru dumneavoastră la sfârșitul cărții. Dar nu vă simțiți prost; ele încă nu
înseamnă nimic nici pentru JavaScript. Oricum, nu numiți o variabilă cu unul dintre următoarele
cuvinte rezervate:
abstract
boolean
byte
char
class
const
debugger
double
enum
export
extends
final
float
goto
implemente
ază
importul
int
interfață
long
pachet
nativ
privat
protejat
public
scurt
static
super
sincronizat
aruncă
tranzitoriu
volatile
Pe lângă cuvintele cheie și cuvintele rezervate, JavaScript are și câteva variabile predefinite.
Astfel, următorii identificatori sunt deja luați:
arguments
Array
Boolean
Date
decodeURI
decodeURIComponent
encodeURI
Eroare
escape
eval
EvalError
Funcția
EvalError
Infinity
isFinite
8
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
isNaN
Math
NaN
Obiect
numeric
parseFloat
parseInt
RangeError
ReferenceError
RegExp
String
SyntaxError
TypeError
undefined
unescape
URIError
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260,
"Half Baked": 4 * 250,
"New York Super Fudge Chunk": 4 * 300,
"Coffee Heath Bar Crunch": 4 * 280, "Cherry
Garcia": 4 * 240,
"Mud Pie": 4 * 270,
"Milk & Cookies": 4 * 270,
"Cinnamon Buns": 4 * 290,
"Chocolate Chip Cookie Dough": 4 * 270,
"Misiune la marțipan": 4 * 260
};
Pentru a interoga un membru în iceCream, tastați iceCream, apoi puneți numele membrului în interiorul []
operator. Haideți să interogăm "Chocolate Fudge Brownie", produsul meu preferat de la Ben & Jerry's,
apoi să verificăm munca noastră cu ajutorul figurii 1-6.
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260,
"Half Baked": 4 * 250,
"New York Super Fudge Chunk": 4 * 300,
"Coffee Heath Bar Crunch": 4 * 280, "Cherry
Garcia": 4 * 240,
"Mud Pie": 4 * 270,
"Milk & Cookies": 4 * 270,
"Cinnamon Buns": 4 * 290,
"Chocolate Chip Cookie Dough": 4 * 270,
"Misiune la marțipan": 4 * 260
};
iceCream["Chocolate Fudge Brownie"]] + " calorii per pint";
// "1040 de calorii pe halbă"
Hmm. Cred că am marcat greșit "Half Baked". Ar trebui să fie 270 pe porție, nu 250. Deci, cum am
putea scrie o nouă valoare în membrul "Half Baked"?
10
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Da, cu operatorul =. Scrierea unui membru este ca și cum ai scrie o variabilă. Haideți să facem acest
lucru în Firebug, verificând munca noastră cu ajutorul figurii 1-7:
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260,
"Half Baked": 4 * 250,
"New York Super Fudge Chunk": 4 * 300,
"Coffee Heath Bar Crunch": 4 * 280, "Cherry
Garcia": 4 * 240,
"Mud Pie": 4 * 270,
"Milk & Cookies": 4 * 270,
"Cinnamon Buns": 4 * 290,
"Chocolate Chip Cookie Dough": 4 * 270,
"Misiune la marțipan": 4 * 260
};
iceCream["Half Baked"] = 4 * 270;
iceCream["Half Baked"]] + " calorii per pint";
// "1080 de calorii pe halbă"
Acum, ce se întâmplă dacă vreau să adaug o nouă aromă, să zicem "Peanut Butter Cup" la înghețată?
Acest lucru funcționează în același mod ca și modificarea valorii unui membru. Așadar, = modifică
valoarea unui membru sau adaugă unul nou. Depinde doar dacă membrul pe care îl interoghezi este
deja definit.
În Firebug, să adăugăm un membru numit "Peanut Butter Cup", astfel. Apoi interogăm valoarea
acestuia, verificând munca noastră cu Figura 1-8:
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260,
"Half Baked": 4 * 270,
"New York Super Fudge Chunk": 4 * 300,
"Coffee Heath Bar Crunch": 4 * 280, "Cherry
Garcia": 4 * 240,
"Mud Pie": 4 * 270,
"Milk & Cookies": 4 * 270,
"Cinnamon Buns": 4 * 290,
11
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Yipes, 1440 de calorii! Dacă stau să mă gândesc mai bine, aș vrea să înlătur asta din înghețată.
Pentru a face acest lucru, treceți membrul "Peanut Butter Cup" la operatorul delete, care, după cum îi
spune și numele, șterge un membru dintr-un obiect. În consecință, atunci când interogăm
iceCream["Peanut Butter Cup"] în urma desființării acestuia, JavaScript returnează undefined pentru a nu
transmite nicio valoare. Totuși, nu putem lipi undefined de un șir de caractere. Așadar, JavaScript îl
convertește mai întâi în "undefined".
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260,
"Half Baked": 4 * 270,
"New York Super Fudge Chunk": 4 * 300,
"Coffee Heath Bar Crunch": 4 * 280, "Cherry
Garcia": 4 * 240,
"Mud Pie": 4 * 270,
"Milk & Cookies": 4 * 270,
"Cinnamon Buns": 4 * 290,
"Chocolate Chip Cookie Dough": 4 * 270,
"Misiune la marțipan": 4 * 260
};
iceCream["Peanut Butter Cup"] = 4 * 360;
delete iceCream["Peanut Butter Cup"];
iceCream["Peanut Butter Cup"]] + " calorii per pint";
// "calorii nedefinite pe halbă"
12
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Pentru a actualiza valoarea unui membru sau pentru a adăuga un nou membru, se utilizează
operatorul =, la fel ca înainte. Să adăugăm un membru bostonCreamPie la iceCream. Apoi interogăm
valoarea acestuia, verificând munca noastră cu ajutorul figurii 1-10:
var iceCream = { chocolateFudgeBrownie:
4 * 260,
pe jumătate coapte: 4 * 270,
NewYorkSuperFudgeChunk: 4 * 300,
coffeeHeathBarCrunch: 4 * 280,
cherryGarcia: 4 * 240,
mudPie: 4 * 270,
prăjiturele cu lapte: 4 * 270,
CinnamonBuns: 4 * 290,
ChocolateChipCookieDough: 4 * 270,
misiuneLaMarzipan: 4 * 260
};
iceCream.bostonCreamPie = 4 * 250;
iceCream.bostonCreamPie + " calorii per pint";
// "1000 de calorii pe halbă"
Figura 1-10. Scrierea unei noi valori într-un membru numit cu un identificator
www.allitebooks.com
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Apoi faceți clic pe Clear (Ștergeți) în ambele panouri Firebug. Apoi, creați o matrice literală
goală numită iceCream, introducând o pereche de paranteze pătrate, urmate, desigur, de un punct
și virgulă.
var iceCream = [
];
Acum adăugați un element la iceCream, astfel:
var iceCream = [
"Chocolate Fudge Brownie"
];
La fel cum membrii unui obiect sunt separați prin virgulă, la fel sunt separate și elementele unui
array. Vom adăuga a doua mea aromă preferată astfel:
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked"
];
Apoi continui să separ elementele cu virgule pentru a completa restul topului meu de zece. Rețineți că
elementul final, "Misiune la Mărțișor", nu este urmat de o virgulă. Observați, de asemenea, că numerele
din JavaScript se gustă de la 0 la 9. Deși "New York Super Fudge Chunk" este 3 în inima mea, pentru
JavaScript este 2:
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Cherry Garcia",
"Mud Pie",
"Milk & Cookies",
"Cinnamon Buns",
"Aluat de biscuiți cu ciocolată",
"Misiune la marțipan"
];
Pentru a interoga un element din iceCream, introduceți numărul acestuia în operatorul []. Rețineți că
numărul unui element este denumit indexul acestuia. Prin urmare, în Firebug, interogați câteva elemente
din iceCream astfel. Nu uitați să vă opriți și să faceți clic pe Run înainte de fiecare comentariu.
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Cherry Garcia",
"Mud Pie",
"Milk & Cookies",
"Cinnamon Buns",
"Aluat de biscuiți cu ciocolată",
"Misiune la marțipan"
];
iceCream[0];
// "Chocolate Fudge Brownie"
iceCream[3];
// "Coffee Heath Bar Crunch"
15
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
iceCream[6];
// "Milk & Cookies"
Verificați munca dvs. cu figura 1-11.
Acum, ce se întâmplă dacă încerc o nouă aromă și vreau să o adaug în top 10. Să zicem că schimb "Misiune la
marțipan" cu
"Boston Cream Pie". Cum ai face asta?
Da, cu operatorul =. Așadar, = scrie o nouă valoare într-un element sau membru. Încercați să faceți acest lucru
în Firebug.
Apoi interogați noul număr 10, înainte de a vă verifica activitatea cu ajutorul figurii 1-12:
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Cherry Garcia",
"Mud Pie",
"Milk & Cookies",
"Cinnamon Buns",
"Aluat de biscuiți cu ciocolată",
"Misiune la marțipan"
];
iceCream[9] = "Boston Cream Pie";
iceCream[9];
// "Boston Cream Pie"
16
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Figura 1-12. Scrierea unei noi valori într-un element dintr-un tablou
Recunoașteți, sunteți sceptic că o matrice este de tipul valorii obiect. Membrii sunt numiți cu șiruri
de caractere sau identificatori, în timp ce elementele sunt numite cu numere. Sau nu?
Nu. Și JavaScript numește elementele cu ajutorul unor șiruri de caractere. Acestea sunt numerice,
dar totuși șiruri de caractere. Așadar, matricea noastră este ca următorul obiect:
var iceCream = {
"0": "Chocolate Fudge Brownie",
"1": "Half Baked",
"2": "New York Super Fudge Chunk",
"3": "Coffee Heath Bar Crunch", "4":
"Cherry Garcia",
"5": "Mud Pie",
"6": "Milk & Cookies",
"7": "Cinnamon Buns",
"8": "Chocolate Chip Cookie Dough", "9":
"Boston Cream Pie"
};
OK, deci dacă elementele tabloului nu sunt numite cu numere, cum se face că citim și scriem valorile lor
prin
număr, nu prin șir de caractere?
Îmi pare rău, JavaScript te-a păcălit din nou. Operatorul [] convertește numărul pe care l-ați introdus
acolo într-un șir de caractere.
Dacă îi dați un 3 pentru a lucra cu el, va returna valoarea elementului numit 3. Pentru a ilustra acest
lucru, interogați un element din iceCream cu un șir de caractere în Firebug:
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Cherry Garcia",
"Mud Pie",
"Milk & Cookies",
"Cinnamon Buns",
"Chocolate Chip Cookie Dough",
17
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
"Misiune la Mărțișor"
];
iceCream["7"];
// "Cinnamon Buns"
Verificați munca dvs. cu figura 1-13.
În mod similar, am putea interoga un membru într-un literal de obiect echivalent cu un număr.
Încercați-o în Firebug, verificând munca dvs. cu ajutorul figurii 1-14:
var iceCream = {
"0": "Chocolate Fudge Brownie",
"1": "Half Baked",
"2": "New York Super Fudge Chunk",
"3": "Coffee Heath Bar Crunch", "4":
"Cherry Garcia",
"5": "Mud Pie",
"6": "Milk & Cookies",
"7": "Cinnamon Buns",
"8": "Chocolate Chip Cookie Dough", "9":
"Misiune la marțipan"
};
iceCream[5];
// "Mud Pie"
18
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
"Mud Pie",
"Milk & Cookies",
"Cinnamon Buns",
"Aluat de biscuiți cu ciocolată",
"Misiune la marțipan"
];
"Cinnamon Buns" === iceCream[0];
// fals
"Cinnamon Buns" === iceCream[1];
// fals
"Cinnamon Buns" === iceCream[2];
// fals
"Cinnamon Buns" === iceCream[3];
// fals
"Cinnamon Buns" === iceCream[4];
// fals
"Cinnamon Buns" === iceCream[5];
// fals
"Cinnamon Buns" === iceCream[6];
// fals
"Cinnamon Buns" === iceCream[7];
// adevărat
Figura 1-15. Determinarea faptului dacă o aromă se află printre primele zece, un adevărat ursuleț
Nu am vrea să facem asta pentru o mulțime de arome. Pentru a elimina acest tip de corvoadă,
JavaScript oferă un al doilea subtip de obiect numit funcție. Pe lângă faptul că pot conține membri sau
elemente, funcțiile pot conține și instrucțiuni. Nu uitați, acestea sunt comenzi pe care le dați lui
JavaScript.
Funcțiile oferă o modalitate de a salva fragmente de cod executat frecvent într-un loc din memorie,
adică pentru reutilizarea codului.
20
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Una dintre acestea ar fi utilă pentru a determina dacă o aromă se află în topul meu de zece. În
Firebug, să salvăm un literal al funcției într-o variabilă numită rankFlavor. Pentru a face acest lucru,
tastați cuvântul cheie function, o pereche de paranteze și o pereche de paranteze curly braces. Rețineți
că parantezele conțin o listă de identificatori separați prin virgulă, denumiți parametri sau argumente.
Aceștia conțin valorile pe care le transmiteți unei funcții atunci când o invocați. Să definim un
parametru de aromă. Apoi, dacă vom trece funcția noastră "Cherry Garcia", JavaScript o va atribui la
aromă.
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Cherry Garcia",
"Mud Pie",
"Milk & Cookies",
"Cinnamon Buns",
"Aluat de biscuiți cu ciocolată",
"Misiune la marțipan"
];
var rankFlavor = function(flavor) {
};
Într-un literal de obiect, parantezele curly conțin membrii, dar într-un literal de funcție,
parantezele curly conțin instrucțiuni. Deocamdată, tastați doar următoarele instrucțiuni for, if și
return. Vom explora if și for în capitolul 4 și return în capitolul 6. Pe scurt, acest fragment de cod
compară valoarea lui flavor cu fiecare element din iceCream. Dacă aroma se află printre primele zece,
JavaScript returnează valoarea expresiei:
flavor + " este numărul " + (i + 1) + ".";
Rețineți că i este indicele elementului în iceCream. În caz contrar, JavaScript returnează această expresie:
flavor + " nu se află printre primele 10 din topul meu.";
Să trecem rankFlavor() "Coffee Heath Bar Crunch" și apoi "Dublin Mudslide" astfel, verificând
munca noastră cu figura 1-16:
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Everything but the...",
"Mud Pie",
"Karamel Sutra",
"Cinnamon Buns",
"Milk & Cookies",
"Misiune la Mărțișor"
];
var rankFlavor = function(flavor) {
for (var i = iceCream.length; i --; ) { if
(iceCream[i] === aroma) {
return flavor + " este numărul " + (i + 1) + " .";
}
}
return flavor + " nu se află în top 10.".";
21
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
};
rankFlavor("Coffee Heath Bar Crunch");
// "Coffee Heath Bar Crunch este numărul
4." rankFlavor("Dublin Mudslide");
// "Dublin Mudslide nu se află în top 10 al meu."
Figura 1-16. Salvarea unui fragment de cod într-o funcție, în loc să îl tastați la nesfârșit
Deși o funcție poate părea foarte diferită de un obiect sau de o matrice, este foarte asemănătoare.
Următorul exemplu ilustrează acest aspect. Aici adăugăm elementele din tabloul înghețată în funcția
rankFlavor. Prin urmare, rankFlavor() conține acum zece elemente în plus față de un fragment de cod.
Dacă modificăm apoi fragmentul de cod astfel încât să se deruleze peste elementele din rankFlavor() și
nu peste cele din iceCream, acesta funcționează la fel de bine, după cum arată figura 1-17:
var rankFlavor = function(flavor) {
for (var i = rankFlavor.len; i --; ) {
if (rankFlavor[i] === flavor) {
return flavor + " este numărul " + (i + 1) + " .";
}
}
return flavor + " nu se află în top 10.".";
};
rankFlavor[0] = " Chocolate Fudge Brownie";
rankFlavor[1] = " Half Baked";
rankFlavor[2] = " New York Super Fudge Fudge
Chunk"; rankFlavor[3] = "Coffee Heath Bar Crunch";
rankFlavor[4] = " Everything but the...";
rankFlavor[5] = "Mud Pie";
rankFlavor[6] = "Karamel Sutra";
rankFlavor[7] = " Cinnamon Buns";
rankFlavor[8] = "Milk & Cookies";
22
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI
Figura 1-17. Funcția rankFlavor() conține acum zece elemente în plus față de un fragment de cod.
Rezumat
În acest capitol, am explorat patru tipuri de valori pentru a reprezenta date. Pentru un text precum
"Chocolate Fudge Brownie", JavaScript are un tip de valoare string. Numerele oferă o modalitate de a face
calcule matematice, în timp ce booleenii spun da și nu.
Tipul de valoare obiect oferă o modalitate de a salva valori conexe în același loc în memorie, ca
un fel de dosar pe computer. Acestea pot fi denumite cu un identificator sau un șir de caractere.
Subtipul array oferă o modalitate de a ordona numeric valorile aferente, în timp ce subtipul funcție
conține fragmente de cod executat frecvent.
Deși obiectele, array-urile sau funcțiile par foarte diferite, toate pot conține membri numiți cu un
șir de caractere sau un identificator sau elemente numite cu un număr întreg nenegativ. Așadar, ele
sunt croite din aceeași stofă.
23
www.allitebooks.com
CHAPTER 2
■■■
Conversie de tip
Iron Man, Superman, Batman, Spider-Man, X-Men și nenumărați alți supereroi au multe trăsături în
comun. Majoritatea au un costum distinctiv, o morală de neclintit, un motiv de bază, o identitate
secretă, super-răufăcători cu care trebuie să lupte și, bineînțeles, puteri extraordinare. Aceste puteri pot
fi sau nu înnăscute, însă. De exemplu, Clark Kent nu are nevoie de costumul lui Superman pentru a
zbura, dar Tony Stark ar cădea ca o piatră fără armura lui Iron Man.
Valorile JavaScript de tip obiect sau array și subtipurile de funcții sunt ca Superman sau Spider-
Man. Ele au în mod înnăscut puteri extraordinare, denumite membri sau metode. Pe de altă parte,
valorile de tip șir de caractere, număr sau boolean sunt precum Iron Man sau Batman, în sensul că
trebuie să își pună costumul, denumit wrapper, pentru a avea puteri extraordinare.
Deci, așa cum când vedeți semnalul liliacului pe cerul nopții deasupra orașului Gotham îi spune
lui Bruce Wayne să îmbrace costumul de liliac pentru a deveni Batman, când vedeți că operatorul .
apare în dreapta voastră îi spune unui șir de caractere, număr sau boolean să îmbrace un înveliș pentru
a deveni un obiect.
În schimb, așa cum Batman redevine Bruce Wayne după ce l-a învins pe Joker, Pinguin sau
Catwoman, un obiect wrapper redevine un șir de caractere, un număr sau un boolean după ce invocă o
metodă. Pentru a converti un șir de caractere, un număr sau un boolean într-un obiect wrapper,
JavaScript invocă String(), Number() sau Boolean(). Acestea sunt denumite funcții de constructor.
Pentru a inversa conversia, adică pentru a converti din nou un obiect wrapper într-un șir de caractere,
număr sau boolean, JavaScript invocă valueOf() pe wrapper.
În măsura în care JavaScript convertește în spatele scenei valorile de tip șir de caractere, număr și
boolean în și din obiecte wrapper, trebuie doar să explorăm caracteristicile acestora. Mai mult,
wrapperele pentru șiruri de caractere sunt utile, dar cele pentru numere și booleeni nu sunt. Așadar, nu
ne vom pierde timpul cu acestea.
Membrii String
Deschideți firebug.html în Firefox, apoi apăsați F12 pentru a activa Firebug. Dacă abia vă alăturați
nouă, întoarceți-vă la prefață pentru detalii despre cum să faceți acest lucru. În capitolul 1, ați învățat
cum să lipiți un șir de altul cu ajutorul operatorului +. concat() face același lucru. Așadar, în Firebug,
haideți să lipim "man" de "Bat" prin intermediul operatorului + și al metodei concat(), verificând
munca noastră cu ajutorul figurii 2-1:
"Bat" + "man";
// "Batman"
"Bat".concat("man");
// "Batman"
25
CAPITOLUL 2 ■ CONVERSIA DE TIP
Dacă doriți să adăugați mai multe șiruri de caractere, separați-le prin virgule. JavaScript va adăuga
apoi secvențial parametrii la șirul inițial. Încercați-o în Firebug introducând și rulând următorul exemplu:
"Spider".concat("-", "Man");
// "Spider-Man"
Un lucru de reținut cu privire la fiecare metodă String pe care am explorat-o în acest capitol este
că acestea returnează un șir nou, modificat, dar nu modifică șirul original. În mod mai formal, am
putea spune că șirurile de caractere sunt imuabile. Pentru a ilustra acest lucru, să invocăm concat() pe
o variabilă care conține un șir de caractere, astfel, verificând munca noastră cu ajutorul figurii 2-2:
var name = "Super";
name.concat("man");
// "Superman"
nume;
// "Super"
26
CAPITOLUL 2 ■ CONVERSIA DE TIP
După cum puteți vedea, JavaScript a folosit șirul de caractere din nume ca bază pentru modificarea pe care am
dorit să o facem.
Metoda concat() a returnat "Superman", dar numele conține în continuare "Super".
Având în vedere acest lucru, probabil că veți dori să salvați valoarea de returnare a unei metode
String într-o altă variabilă. În caz contrar, este ca și cum modificarea nu ar fi avut loc niciodată. Haideți
să facem acest lucru, verificând munca noastră cu ajutorul figurii 2-3:
var pre = "Bat";
var post = pre.concat("man");
pre;
// "Bat"
post;
// "Batman";
27
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-3. De obicei, veți dori să salvați valoarea de returnare. În caz contrar, aceasta se pierde.
Dacă nu aveți nevoie de șirul de caractere original, îl puteți suprascrie pur și simplu cu valoarea de
returnare, astfel. Rețineți că acest lucru nu modifică șirul original. Mai degrabă, scrie un nou șir în
variabilă:
var pre = "Bat";
pre = pre.concat("man");
pre;
// "Batman"
În mod ciudat, este destul de obișnuit să nu se salveze valoarea de returnare a unei metode de tip șir de
caractere. Să zicem că doriți să faceți o comparație insensibilă la majuscule și minuscule între un șir și
altul. Poate c ă nu sunteți sigur dacă un vizitator va căuta "Superman", "superman" sau "SuperMan".
Pentru a face acest lucru, ați apela toLowerCase() pe un șir de caractere, comparând valoarea de returnare,
un literal minuscul, cu un alt literal minuscul, astfel, verificând munca dvs. cu ajutorul figurii 2-4:
var hero = "Superman";
hero.toLowerCase() === "superman";
// adevărat
28
CAPITOLUL 2 ■ CONVERSIA DE TIP
Înțelegerea celor trei moduri de utilizare a valorii de returnare a oricărei metode String este la fel
de importantă ca și cunoașterea funcției acesteia. Iată o recapitulare:
String.toLocaleUpperCase()
String.toLowerCase()
String.toUpperCase()
■ Notă Folosim String.fromCharCode() așa cum este, deoarece este o metodă statică. Aceasta înseamnă că
JavaScript nu utilizează metoda constructorului String() pentru a crea un șir de caractere atunci când
apelăm această metodă.
Învelișurile de șiruri au un membru length egal cu numărul de elemente. Altfel spus, lungimea este
egală cu numărul de caractere din șir. Încercați să interogați lungimea pentru Incredibilii și câțiva
dintre super-răufăcătorii lor.
30
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-6. Interogarea elementelor în mod standard este mai puțin convenabilă, dar funcționează între browsere.
31
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-7. String.fromCharCode() oferă o modalitate de a insera caractere care nu se află pe tastatură.
În schimb, este mai simplu să codificați ü și să lucrați cu 252, de exemplu într-o comparație,
decât să încercați să tastați ü. Pentru a face acest lucru, treceți indexul la charCodeAt(), care
returnează codificarea Unicode și nu caracterul, așa cum ar face partenerul său, charAt(). Deși
următoarele două comparații sunt echivalente, bănuiesc că ați putut să o introduceți doar pe prima în
Firebug. Totuși, figura 2-8 le afișează pe amândouă.
var id = "Dr. Otto G" + String.fromCharCode(252) + "nther Octavius";
id.charCodeAt(10) === 252;
// adevărat
id.charAt(10) === "ü";
// adevărat
32
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-8. charCodeAt() este utilă pentru codificarea caracterelor care nu se află pe tastatură.
Caz de conversie
Pe lângă decodarea și codificarea caracterelor cu String.fromCharCode() și charCodeAt(), puteți
converti caracterele în minuscule sau majuscule cu toLowerCase() sau toUpperCase(). De exemplu,
scenele de luptă din benzile desenate Batman ar avea cuvinte onomatopeice precum pow, bam și zonk
suprapuse cu majuscule. Așadar, în Firebug, haideți să adăugăm un pic de forță unor cuvinte
onomatopeice minuscule cu ajutorul toUpperCase():
"Pow! Bam! Zonk!".toUpperCase();
// "POW! BAM! ZONK!"
În schimb, dacă Pinguinul ar pulveriza în liniște un gaz paralizant asupra lui Batman și Robin cu
umbrela sa, am putea dori să reducem "PSST...ZZZZ" cu toLowerCase(), verificând ambele mostre cu
figura 2-9. Rețineți că toLowerCase() sau toUpperCase() manipulează doar literele. Așadar, nu se va
întâmpla nimic ciudat, cum ar fi transformarea lui "!" în "1".
"PSST...ZZZZ".toLowerCase();
// "psst...zzzz"
33
CAPITOLUL 2 ■ CONVERSIA DE TIP
• İi
• Iı
Versiunea minusculă a lui I este ı, nu i. Invers, versiunea majusculă a lui i este İ, nu I. Deci, pentru
turcă, toLowerCase() și toLowerCase() ar încurca perechile i. Pentru limba turcă și alte alfabete cu versiuni
i cu sau fără puncte, cum ar fi azeră, kazahă, tătară și tătară din Crimeea, JavaScript oferă o a doua pereche
de metode, toLocaleLowerCase() și toLocaleUpperCase(), care fac conversiile i corect:
"I".toLowerCase();
// " i"
"i".toUpperCase()
// " I"
"I".toLocaleLowerCase();
// "ı"
"i".toLocaleUpperCase()
// "İ"
34
www.allitebooks.com
CAPITOLUL 2 ■ CONVERSIA DE TIP
Rețineți că puteți apela indexOf() pentru orice expresie care se evaluează la string. Printre acestea se
numără literalele și variabilele, precum și valorile de returnare pentru operatori sau funcții, pe care le vom
aborda în capitolele 3 și, respectiv, 6.
indexOf() acceptă opțional un al doilea parametru care îi indică lui JavaScript unde să înceapă să
caute o subșir. În măsura în care indexOf() returnează locația primei corespondențe, cel de-al doilea
parametru oferă un parametru
35
CAPITOLUL 2 ■ CONVERSIA DE TIP
modalitate de a localiza un subșir care se repetă, cum ar fi "Man" în lista noastră de super răufăcători
Iron Man. Astfel, am putea localiza prima și a doua apariție a lui "Man" în felul următor în Firebug.
Rețineți că JavaScript evaluează villains.indexOf("Man") + 1, care returnează 23, înainte de a trece
parametrul la indexOf(). Verificați munca dvs. cu ajutorul figurii 2-11:
var villains = "Iron Monger, Titanium Man, Madame Masque, Ghost, Mandarin";
villains.indexOf("Man");
// 22
villains.indexOf("Man", villains.indexOf("Man")) + 1);
// 49
indexOf() are un partener în crimă numit lastIndexOf() care caută în amonte, de la sfârșitul unui
șir de caractere până la început. În măsura în care a doua apariție a lui "Man" este și ultima, am putea
rescrie exemplul anterior astfel:
var villains = "Iron Monger, Titanium Man, Madame Masque, Ghost, Mandarin";
villains.lastIndexOf("Man");
// 49
Rețineți că, dacă omiteți al doilea parametru, JavaScript taie o felie de la indexul din primul
parametru până la sfârșitul șirului. Altfel spus, setează al doilea index la lungime. Așadar,
următoarele două exemple fac același lucru:
var heroes = "Superman, Batman, Spider-Man, Iron Man";
heroes.slice(30);
// "Iron Man"
heroes.slice(30, heroes.length);
// "Iron Man"
Rețineți, de asemenea, că, dacă oricare dintre parametri este negativ, JavaScript le adaugă lungimea.
Verificați munca dvs. cu ajutorul figurii 2-12:
heroes.slice(10, -22);
// "Batman"
• Șirul de eliminat
• Șirul de inserat
Cu toate acestea, primul parametru poate fi un șir de caractere sau un obiect RegExp (vom explora
obiectele RegExp în capitolul 5), iar al doilea parametru poate fi un șir de caractere sau o funcție care
returnează un șir de caractere.
37
CAPITOLUL 2 ■ CONVERSIA DE TIP
■ Notă Nu vă faceți griji cu privire la exemplele RegExp din acest capitol; acestea sunt destul de simple și sunt
incluse pentru a păstra unele sarcini comune într-un singur loc. Acest lucru înseamnă că veți învăța cele mai bune
modalități de înlocuire a subșirurilor aici, în loc să t r e b u i a s c ă s ă așteptați până la capitolul 5
pentru a învăța toate tehnicile.
Pentru început, vom face ambii parametri șiruri de caractere. Așadar, curățați de două ori Firebug,
așa cum este detaliat în prefață, și să folosim replace() pentru a-l transforma pe Batman în Superman,
astfel:
"Batman".replace("Bat", "Super");
// "Superman"
Un lucru de reținut atunci când se trece un șir de caractere în loc de un obiect RegExp pentru primul
parametru este că
replace() schimbă doar prima apariție. Pentru a ilustra acest lucru, rulați următorul exemplu în Firebug:
"Batman și Batgirl".replace("Bat", "Super");
// "Superman și Batgirl"
Pentru a înlocui două sau mai multe apariții ale unui șir de căutare precum "Bat" cu un șir de
înlocuire precum "Super", primul parametru trebuie să fie un obiect RegExp marcat cu un indicator g,
care îi spune lui JavaScript să caute toate corespondențele și nu doar prima corespondență. Așadar,
doar ca o introducere pentru capitolul 5, dacă facem din primul parametru din exemplul anterior un literal
RegExp foarte simplu, /Bat/g, obținem duo-ul dorit. Verificați acest lucru și cele două exemple anterioare
cu Figura 2-13:
"Batman și Batgirl".replace(/Bat/g, "Super");
// "Superman și Supergirl"
38
CAPITOLUL 2 ■ CONVERSIA DE TIP
toUpperCase() pentru a converti prima literă din m în majusculă. Apoi, vom lipi acest lucru de o felie care
conține toate caracterele din m, cu excepția primului, și o vom returna ca șir de înlocuire.
var titleCase = function(m) {
return m . s l i c e (0,1).toUpperCase() + m.slice(1);
};
Dacă trecem apoi un literal RegExp care se potrivește cu cuvintele, /\b\w+\b/g, pentru primul parametru,
JavaScript
va trece fiecare cuvânt din șirul pe care îl apelăm replace() la titleCase(). Să încercăm acest lucru cu
"batman, spiderman, iron man", verificând munca noastră cu ajutorul figurii 2-14. Observați că JavaScript
invocă titleCase() de cinci ori, o dată pentru fiecare dintre următoarele potriviri: "batman", "spider", "man",
"iron" și "man".
var titleCase = function(m) {
return m . s l i c e (0,1).toUpperCase() + m.slice(1);
};
"batman, spider-man, iron man".replace(/\b\w+\b/g, titleCase);
// "Batman, Spider-Man, Iron Man"
Figura 2-15. Împărțirea unui șir de caractere în șiruri mai mici cu split()
Să presupunem că răufăcătorul final este precedat de ", și " și nu de ", ". Altfel spus, dorim să
împărțim un șir pe baza a doi divizori, ", " sau ", și ". Acest lucru nu se poate face prin trecerea
unui divizor de șir. Mai degrabă, ar trebui să transmitem un literal RegExp pentru a se potrivi cu
ambii divizori:
/, (?:și )?/g
Nu vă faceți griji, nu va mai părea o vorbă goală până la sfârșitul capitolului 5 (observați cum puteți
identifica divizorii din expresie și că folosim din nou /g). Încercați următoarea mostră în Firebug,
verificând munca dvs. cu Figura 2-16:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin, and Sandman";
villains.split(/, (?:and )?/g);
// ["Green Goblin", " Doctor Octopus", "Venom", " Hobgoblin", "Sandman"]
40
CAPITOLUL 2 ■ CONVERSIA DE TIP
41
CAPITOLUL 2 ■ CONVERSIA DE TIP
După cum s-a menționat mai devreme în acest capitol, split(), ca orice altă metodă de șir de
caractere, nu modifică valoarea șirului de caractere pe care este apelată. Mai degrabă, split() returnează
o nouă valoare. Ar trebui să salvăm matricea într-o nouă variabilă sau să suprascriem răufăcătorii. Să
facem aceasta din urmă, verificând munca noastră cu ajutorul figurii 2-18:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin, and Sandman";
villains = villains.split(/, (?:and )?/g);
răufăcători[1];
// "Doctor Octopus"
Figura 2-18. Suprascrierea șirului de caractere din villains cu matricea returnată de split()
42
CAPITOLUL 2 ■ CONVERSIA DE TIP
propertyIsEnumerable()
toLocaleString()
toString()
valueOf()
valueOf() returnează șirul de caractere, numărul sau booleanul asociat cu un obiect wrapper. Cu
alte cuvinte, JavaScript invocă valueOf() asupra unui wrapper pentru a-l transforma într-un șir de
caractere, număr sau boolean. Așadar, în Firebug, putem face în mod explicit ceea ce JavaScript face în
mod implicit prin crearea unui wrapper cu new și String(), Number() sau Boolean(); interogarea unui
membru sau invocarea unei metode; și apoi invocarea valueOf(). Verificați munca dvs. cu ajutorul
figurii 2-20.
var pre = new String("Hob");
var post = p r e .concat("goblin");
pre = pre.valueOf();
pre;
// "Hob"
post;
// "Hobgoblin"
Figura 2-20. Conversia explicită a unui șir de caractere în și dintr-un obiect wrapper
JavaScript nu transformă imediat un wrapper creat în mod explicit într-un șir de caractere, număr
sau boolean. Acestea oferă o modalitate de a crea un wrapper care persistă dincolo de o singură linie
de cod.
www.allitebooks.com
CAPITOLUL 2 ■ CONVERSIA DE TIP
Invocarea String(), Number() sau Boolean() cu ajutorul operatorului new creează un obiect
wrapper. Pe de altă parte, dacă se omite new, parametrul este convertit într-un șir de caractere, număr
sau boolean. Conversia unei valori într-un tip diferit este un alt lucru pe care JavaScript îl face în spatele
scenei. Astfel, așa cum JavaScript convertește discret un șir de caractere într-un wrapper, adică în tipul
obiect, tot așa convertește discret un șir de caractere în tipul număr sau boolean.
Te întrebi de ce ar avea nevoie JavaScript să facă asta pentru tine, nu-i așa? În primul rând, operatorii
pe care îi vom explora necesită, de obicei, ca operanzii lor să fie de un anumit tip. Astfel, în cazul în
care le dați o valoare de tip greșit, JavaScript vă salvează pielea prin convertirea acesteia în tipul corect.
Pe de altă parte, controlul fluxului cu ajutorul instrucțiunilor condiționale, pe care îl vom explora în
capitolul 4, se bazează pe faptul că JavaScript convertește valorile în tipul boolean. La rândul său, acest
lucru înseamnă că fiecare valoare pe care ați putea-o crea are un echivalent boolean. Cele care se
convertesc la true sunt denumite valori truey, în timp ce cele care se convertesc la false sunt denumite
valori false. Există doar șase valori false, care sunt enumerate aici, astfel încât toate celelalte valori se
convertesc la true:
nedefinit
null false
""
0
NaN
Dar nu mă credeți pe cuvânt. Curățați de două ori Firebug, apoi treceți-le pe rând pe fiecare dintre
acestea la
Boolean(), verificând munca dvs. cu figura 2-21:
Boolean(nedefinit);
// fals
Boolean(null);
// fals
Boolean(false);
// fals
Boolean("");
// fals
Boolean(0);
// fals
Boolean(NaN);
// fals
45
CAPITOLUL 2 ■ CONVERSIA DE TIP
■ Notă Conversia undefined, valoarea unei metode sau a unui membru lipsă, în false este baza pentru
testarea caracteristicilor, pe care o vom face destul de mult în ultimele capitole.
Orice altă valoare se transformă în true. Așadar, orice șir de caractere, cu excepția lui "", orice număr,
cu excepția lui 0 și NaN, și orice obiect se convertește la true:
Boolean("Mr. Incredible");
// adevărat
Boolean(["Green Goblin", " Doctor Octopus", " Venom", " Hobgoblin", "Sandman"]);
// true
Boolean(String.fromCharCode);
// adevărat
Rețineți, de asemenea, că, dacă oricare dintre operanzii unui operator matematic, cum ar fi * sau -, este
NaN, valoarea de returnare va fi, de asemenea, NaN. Prin urmare, după cum arată figura 2-22, puteți face
calcule matematice cu null, dar nu și cu undefined.
var n o t h i n g , zilch =
null; nothing * 4;
// NaN
zilch * 4;
// 0
Figura 2-22. JavaScript poate face calcule matematice cu null, dar nu și cu undefined.
O problemă frecventă este încercarea de a face calcule matematice cu o valoare CSS; JavaScript
reprezintă toate aceste valori ca șiruri de caractere. Astfel, dacă încercați să mutați un element cu 3
pixeli spre stânga, scăzând 3 dintr-o valoare de stânga de, să zicem, 30px, faceți de fapt următorul
calcul. Rețineți că manipularea CSS este tratată în capitolul 8.
"30px" - 3;
// NaN
Conversia booleanilor în numere este foarte simplă. true se c o n v e r t e ș t e î n 1, iar false se
c o n v e r t e ș t e în 0. Încercați să faceți acest lucru în Firebug:
Număr(adevărat);
// 1
Număr(fals);
// 0
Rareori un obiect, un tablou sau o funcție se va converti într-un număr diferit de NaN. Încercarea
de a face calcule matematice cu un obiect, un tablou sau o valoare de funcție va returna, în general, NaN
pentru a indica eșecul. Încercați să convertiți una din fiecare în Firebug, verificând munca dvs. cu Figura
2-24:
Număr(["Green Goblin", " Doctor Octopus", "Sandman"]);
// NaN
Număr({erou: "Batman", dușman: "Joker"});
// NaN
Number(String.fromCharCode);
// NaN
48
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-24. Cele mai multe valori de obiect, matrice și funcție se convertesc în NaN.
nedefinit NaN
null 0
"" 0
"30px" NaN
"4" 4
"3.33" 3.33
adevărat 1
fals 0
String.fromCharCode NaN
49
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-25. Conversia unei matrice și a unui obiect într-un șir de caractere
Următoarea metodă, toFixed(), convertește un număr într-un șir zecimal. Opțional, puteți indica
numărul de zecimale prin trecerea unui număr între 0 și 20. Să împărțim 299792458 la 1000 pentru a
determina kilometri pe secundă. Apoi, convertiți acest rezultat într-un șir de caractere cu trei sau fără
zecimale, verificând munca dvs. cu ajutorul figurii 2-27. Rețineți că omiterea parametrului este același
lucru cu trecerea lui 0.
(299792458 / 1000).toFixed(3);
// "299792.458"
(299792458 / 1000).toFixed();
//"299792"
Dacă sunteți indecis și doriți ca JavaScript să aleagă între formatul exponențial și cel zecimal,
apelați toPrecision() pentru număr. Parametrul opțional diferă de această dată: este un număr între 1 și
21 care indică numărul de cifre semnificative. În cazul în care parametrul este mai mic decât numărul
de cifre din partea întreagă a numărului, JavaScript alege formatul exponențial. În caz contrar,
JavaScript alege formatul zecimal. În cele din urmă, dacă omiteți parametrul, JavaScript invocă
Number.toString(). Încercați următoarele exemple pentru a clarifica modul în care funcționează
toPrecision(), verificând munca dvs. cu ajutorul figurii 2-28. Rețineți că ultimele două exemple sunt
echivalente.
(299792458).toPrecision(2);
// "3.0e+8"
(299792458).toPrecision(12);
// "299792458.000"
(299792458).toPrecision();
// "299792458"
(299792458).toString();
// "299792458"
Rețineți că toExponential(), toFixed() și toPrecision() rotunjesc cifrele din urmă 0-4 în jos și 5-9
în sus, așa cum ați face-o dumneavoastră.
53
CAPITOLUL 2 ■ CONVERSIA DE TIP
Pentru a ilustra această caracteristică de conversie a șirurilor JavaScript în RegExp, curățați de două
ori Firebug, apoi introduceți și rulați următorul exemplu. După cum arată figura 2-29, trecerea unui
șir de caractere la match() și search() funcționează perfect:
var incredibles = "Mr. Incredible, Elastigirl, Violet, Dash, Jack-Jack"; incredibles.match("Jack");
// ["Jack"]
incredibles.search("Jack");
// 42
Figura 2-29. JavaScript convertește liniștit "Jack" în /Jack/ atât pentru match(), cât și pentru search().
JavaScript a transmis în mod discret "Jack" la RegExp(), care, ca și String(), este denumită funcție
constructoare. Așadar, pentru a face în mod explicit ceea ce JavaScript a făcut în mod implicit, să
introducem și să rulăm următoarele în Firebug. După cum arată figura 2-30, valorile de returnare sunt
aceleași:
var incredibles = "Mr. Incredible, Elastigirl, Violet, Dash, Jack-Jack"; incredibles.match(new
RegExp("Jack")));
// ["Jack"]
incredibles.search(new RegExp("Jack"));
// 42
54
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-30. Transmiterea unei RegExp pentru un șir literal către match() și search()
Rețineți, totuși, că atunci când JavaScript convertește un șir de caractere într-un obiect RegExp, nu
sunt setate stegulețele g, i și m, pe care le vom analiza în capitolul 5. Nu există nicio modalitate prin care
JavaScript să salveze situația dacă am intenționat să transmitem /jack/ig, dar în schimb am transmis
"jack" la match(), așa cum arată figura 2-31:
var incredibles = "Mr. Incredible, Elastigirl, Violet, Dash, Jack-Jack";
incredibles.match(/jack/ig);
// ["Jack", "Jack"]
incredibles.match("jack");
// nul
Rețineți că match() transmite eșecul, adică lipsa unui tablou de șiruri de caractere
corespunzătoare, prin returnarea lui null. Amintiți-vă din capitolul 1 că null nu transmite nicio valoare
pe heap, cu alte cuvinte, niciun obiect, matrice sau funcție. Acesta este motivul pentru care match() a
returnat null în loc de undefined.
55
CAPITOLUL 2 ■ CONVERSIA DE TIP
Figura 2-31. JavaScript nu setează indicatorul i, g sau m atunci când convertește un șir de caractere într-un obiect
RegExp.
Rezumat
În acest capitol, ați învățat că JavaScript convertește un șir de caractere într-un obiect wrapper ori de
câte ori șirul este operandul stâng al operatorului . sau [], trecând discret șirul în constructorul
String() și apoi, la fel de discret, transformând wrapperul într-un șir de caractere prin invocarea
metodei sale valueOf(). Învelișurile de șiruri de caractere manipulează caracterele ca și cum ar fi
elemente numai de citit. Cu alte cuvinte, valorile șirurilor de caractere sunt imuabile, iar metodele de
înveliș returnează o nouă valoare fără a modifica valoarea originală. Așadar, de obicei, veți dori să
salvați valoarea returnată sau să o treceți imediat la unul dintre operatorii pe care îi vom explora în
capitolul 3.
Cei mai mulți operatori JavaScript sunt foarte atenți la tipul de valoare al operanzilor lor. Știind
acest lucru, JavaScript va salva situația prin convertirea unui operand de tip greșit în unul corect. Deși
JavaScript face acest lucru în spatele scenei, am explorat cum să facem acest lucru trecând valori către
String(), Number(), Boolean() și Object(). Dacă sunt invocați fără operatorul new, acești constructori
își convertesc argumentul într-un șir de caractere, număr, boolean sau obiect. Astfel, un șir de caractere
poate fi convertit într-un wrapper prin transmiterea lui la Object() fără new sau la String() cu new:
Object("Spider-Man");
new String("Spider-Man");
Astfel, atunci când JavaScript creează un înveliș pentru un șir de caractere, convertește de fapt
valoarea din tipul de șir de caractere în tipul de obiect. Același lucru este valabil și pentru învelișurile
de tip număr sau boolean. Prin urmare, conversia tipului de valoare este vitală pentru manipularea
valorilor cu metode de înveliș sau cu operatori. Veți afla mai multe despre aceștia din urmă în capitolul
3. Luați o pauză și ne vedem acolo!
56
CHAPTER 3
■■■
Operatori
Nu are rost să ai date dacă nu poți face nimic cu ele, așa că fă un pas înainte, operatorule. Operatorii
fac exact ceea ce sugerează numele lor: operează asupra lucrurilor. În cazul JavaScript, aceste lucruri
sunt valorile stocate în variabile și orice altă valoare utilizată în scriptul dumneavoastră. De exemplu,
am folosi un operator de divizare pentru a împărți o valoare la o altă valoare pentru a returna un
rezultat. Există câțiva operatori foarte simpli în JavaScript și câțiva operatori mai puțin simpli; îi vom
acoperi pe toți cei utili și îi vom menționa pe ceilalți în trecere, chiar dacă nu îi veți folosi niciodată.
Pentru a lucra cu operatorii, trebuie să înțelegeți mai întâi precedența operatorilor, care este modul în
care JavaScript decide ce operator să aplice primul dacă există mai mulți într-o e x p r e s i e , și
asociativitatea operatorilor, care este direcția în care JavaScript citește o expresie atunci când aplică
operatorii. Vom examina precedența și asociativitatea ca teorie generală la începutul capitolului și apoi
vom vedea cum funcționează cu adevărat operatorii JavaScript. Pe parcursul capitolului, veți învăța cum
să folosiți operatorii în mod eficient, astfel încât expresiile dumneavoastră să fie cu adevărat zburdalnice.
• 1 linguriță de sifon
• 1 ou
Amintiți-vă din capitolul 1 că o expresie este ca o rețetă prin care operatorii combină ingrediente
denumite operanzi pentru a crea o valoare. Așadar, având în minte această analogie, am putea scrie o
expresie pentru aluat astfel:
var aluat = aluatFăină de patiserie + migdale + scorțișoară de saigon + lămâie + sare de mare + sodă + tartru +
iaurt de vanilie + ou + afine sălbatice;
În măsura în care îi spune lui JavaScript să combine orbește ingredientele de la stânga la dreapta,
nu numai că nu ar reuși să facă să dospească sifonul, dar afinele sălbatice și lămâia ar fi fost strivite de
mixer. Ne-am alege cu un sâmbure violet foarte acru în loc de o pâine delicioasă. Cred că m-aș duce la
culcare flămândă.
Mai degrabă decât să amestecăm totul secvențial, dorim ca JavaScript să facă opt pași în mod asincron:
2. Cernerea este următoarea, deoarece S are prioritate 11. Dar există patru operații S
la rând, așa că JavaScript apelează la asociativitate, care este L pentru cernere,
pentru a determina ordinea operațiilor. Prin urmare, JavaScript le grupează după
cum urmează, evaluându-le de la stânga la dreapta:
(((făină S migdale) S scorțișoară) S lămâie) S sare de mare) S sare de mare
3. În măsura în care W are 10 priorități față de 3 pentru K și F, JavaScript este următorul
c a r e bate. Există patru operații W la rând. Asociativitatea, care este R pentru W, îi
spune lui JavaScript să le facă de la dreapta la stânga. Prin urmare, JavaScript
grupează operațiile W după cum urmează. Rețineți că faptul că W are asociativitate R
nu înseamnă că JavaScript bate operandul din dreapta cu operandul din stânga.
Ingrediente cernute W (sifon W (tartru W (iaurt de vanilie W ou))))
4. Acum avem un K urmat de un F, ambele cu prioritate 3. Cu toate acestea, K are
asociativitatea R, iar F are L. JavaScript face mai întâi K sau F? Da, F se pliază.
R urmat de R sau L înseamnă că îl face mai întâi pe cel din dreapta. În schimb,
L urmat de L sau R înseamnă că o face mai întâi pe cea din stânga. Deci,
JavaScript se pliază în wildBlueberries.
5. Acum trebuie doar să frământăm aluatul timp de un minut sau două pentru
a forma fire de gluten care să rețină bulele, să dăm forma pâinii, să tăiem
un X puțin adânc în partea de sus și am terminat.
• Operatorii unari au asociativitatea R și precedența 14, cu excepția lui new, care are
precedența 15.
• Operatorii de atribuire binară (=, *=, /=, %=, +=, -=) au asociativitate R și
precedență 2. Toți ceilalți operatori binari au asociativitate L și precedență
variabilă.
■ Notă JavaScript are un grup de operatori numiți operatori bitwise. Operatorii bitwise sunt în general foarte rapizi
în alte limbaje de programare, dar sunt foarte lenți în JavaScript. Așadar, nimeni nu face manipulare de biți cu
JavaScript. Nici noi nu o vom face, așa că nu îi voi aborda în această carte.
61
CAPITOLUL 3 ■ OPERATORI
dough.pourableVanillaYogurt[0] *= 3;
dough.egg[0] *= 3;
aluat.wildBlueberries[0] *= 3;
aluat.pastryFlour[0];
// 5.25
dough.pourableVanillaYogurt[0];
// 3
Ceea ce urmează este ceea ce ar fi trebuit să tastăm dacă nu am fi avut *=, astfel încât *= ne-a scutit de
introducerea separată a tastelor = și *, așa cum arată figura 3-2.
var aluat = {
făină de patiserie: [ 1 + 3/4, " ceașcă"],
făină de migdale: [1/3, "ceașcă"],
scorțișoară de saigon: [1, "linguriță"],
lămâie tocată măruntZest: [2,
"tsp"], sare de mare: [1/4,
"tsp"],
sifon: [1, "tsp"],
tartru: [1, "tsp"],
iaurt cu vanilie: [1, "cup"],
ou: [1],
WildBlueberries: [1 + 1/4, "ceașcă"]
};
dough.pastryFlour[0] = dough.pastryFlour[0] * 3 ;
d o u g h . almondFlour[0] = d o u g h .almondFlour[0] * 3 ;
d o u g h . saigonCinnamon[0] = d o u g h .saigonCinnamon[0] * 3;
dough.mincedLemonZest[0] = dough.mincedLemonZest[0] * 3;
dough.seaSalt[0] = dough.seaSalt[0] * 3;
dough.soda[0] = dough.soda[0] * 3;
62
CAPITOLUL 3 ■ OPERATORI
dough.tartar[0] = dough.tartar[0] * 3 ;
d o u g h . pourableVanillaYogurt[0] = d o u g h .pourableVanillaYogurt[0]
* 3; dough.wildBlueberries[0] = dough.wildBlueberries[0] * 3;
dough.pastryFlour[0];
// 5.25
dough.pourableVanillaYogurt[0];
// 3
■ Notă Bucla for in, pe care o vom aborda în Capitolul 4, ar elimina și mai mult corvoada.
Ca și operatorul de înmulțire *, *= își convertește operanzii în numere, dacă este necesar. Astfel,
dacă am definit din greșeală unele elemente cu șiruri de caractere, JavaScript le va converti în numere,
astfel încât partea de înmulțire a *= să funcționeze. În măsura în care *= face și atribuire, valorile sunt
convertite permanent din șiruri de caractere în numere.
La fel ca *=, -= și /= își convertesc operanzii în numere. Cu toate acestea, += favorizează lipirea
șirurilor de caractere în locul adăugării numerelor, la fel ca +. Astfel, dacă un operand este un șir de
caractere, iar celălalt este un număr, boolean, obiect, null sau nedefinit, += c o n v e r t e ș t e non-șirul de
caractere într-un șir de caractere. Prin urmare, += va efectua adunarea numai dacă un operand este un
număr și celălalt nu este un șir de caractere.
Pentru a ilustra, încercați să dublați pourableVanillaYogurt cu += în loc de *=. După cum arată figura 3-3, +=
convertește operandul său stâng 1 în 1 și apoi îl lipește de operandul său drept pentru a crea șirul 11.
var aluat = {
făină de patiserie: [ 1 + 3/4, " ceașcă"],
făină de migdale: [1/3, "ceașcă"],
scorțișoară de saigon: [1, "linguriță"],
63
CAPITOLUL 3 ■ OPERATORI
Figura 3-3. += realizează concatenarea numai dacă operandul său din dreapta nu este un număr.
Rețineți că orice valoare JavaScript poate fi convertită în boolean sau șir de caractere, dar nu și în număr.
Oricum, nu una cu care să poți face matematică. Cele mai multe numere care nu sunt numere se convertesc în
literalul "not a number" NaN. În plus, valoarea de returnare a oricărei operații matematice care conține un
operand NaN va fi întotdeauna NaN.
În măsura în care JavaScript returnează NaN ori de câte ori o valoare nu poate fi convertită într-un număr, +=, -
=, *= și
/= poate suprascrie o variabilă, un membru, un element sau un parametru cu NaN. Pentru a ilustra a c e s t
aspect, încercați următorul exemplu, verificând munca dvs. cu ajutorul figurii 3-4. În măsura în care am uitat să
ne rafinăm interogarea cu operatorul [], JavaScript înmulțește matricea [1 + 1/4, "ceașcă"] cu 3. Prin urmare,
[1 + 1 / 4 , "ceașcă"] este c o n v e r t i t în numărul NaN și înmulțit cu 3. Astfel, matricea din
dough.wildBlueberries este suprascrisă c u valoarea de returnare NaN * 3, care, desigur, este NaN.
var aluat = {
făină de patiserie: [ 1 + 3/4, " ceașcă"],
făină de migdale: [1/3, "ceașcă"],
scorțișoară de saigon: [1, "linguriță"],
lămâie tocată măruntZest: [2,
"tsp"], sare de mare: [1/4,
"tsp"],
sifon: [1, "tsp"],
tartru: [1, "tsp"],
iaurt cu vanilie: [1, "cup"],
ou: [1],
WildBlueberries: [1 + 1/4, "ceașcă"]
};
64
CAPITOLUL 3 ■ OPERATORI
dough.wildBlueberries *= 3;
dough.wildBlueberries;
// NaN
Figura 3-4. În cazul în care nu reușim să rafinăm interogarea noastră cu [], rezultă că matricea este suprascrisă
cu NaN.
OK, deci ce s-ar întâmpla dacă am încerca să interogăm primul element din
dough.wildBlueberries acum că este NaN, nu un array?
Iată un indiciu: consultați capitolul 2.
Da, dacă interoghezi NaN cu operatorii . sau [], JavaScript creează un înveliș de număr pentru NaN.
Deoarece acest înveliș de numere nu conține niciun element, interogarea primului său element returnează un
rezultat nedefinit, după c u m ilustrează următorul exemplu și figura 3-5. Rețineți că + convertește undefined în
" undefined" înainte de a-l lipi de "There are ". Rețineți, de asemenea, că găsirea și repararea greșelilor de
codare de acest tip reprezintă ceea ce presupune în primul rând depanarea.
var aluat = {
făină de patiserie: [ 1 + 3/4, " ceașcă"],
făină de migdale: [1/3, "ceașcă"],
scorțișoară de saigon: [1, "linguriță"],
lămâie tocată măruntZest: [2,
"tsp"], sare de mare: [1/4,
"tsp"],
sifon: [1, "tsp"],
tartru: [1, "tsp"],
iaurt cu vanilie: [1, "cup"],
ou: [1],
WildBlueberries: [1 + 1/4, "ceașcă"]
};
aluat.wildBlueberries *= 3;
"Există " + dough.wildBlueberries[0] + " căni de afine sălbatice în aluat.".";
// "În aluat sunt căni nedefinite de afine sălbatice."
65
CAPITOLUL 3 ■ OPERATORI
Figura 3-5. JavaScript convertește NaN într-un obiect wrapper dacă îl interogați cu operatorul . sau [].
În cele din urmă, este esențial să rețineți că operandul din stânga la +=, -=, *= sau /= trebuie să fie
o variabilă, un membru, un element sau un parametru. În caz contrar, JavaScript vă va da o palmă
peste cap prin returnarea unui SyntaxError cu mențiunea "invalid assignment left-hand side", ceea ce
înseamnă că operandul din stânga trebuie să fie unul dintre aceste lucruri.
3 -= 1;
// SyntaxError: atribuire invalidă în partea stângă { message="invalid assignment left-hand side"
}}
"albastru" += "fructe de pădure";
// SyntaxError: atribuire invalidă în partea stângă { message="invalid assignment left-hand side"
}}
aluat.saigonCinnamon[0];
// 2
dough.mincedLemonZest[0];
// 1
Rețineți că ++ și -- pot apărea în pozițiile prefix sau postfix, adică la stânga sau la dreapta
operandului lor. Pozițiile prefix și postfix sunt irelevante pentru noua valoare a operandului: ++ va
adăuga 1 la operandul său, iar -- va scădea 1 din operandul său în ambele sensuri. Cu toate acestea,
valoarea de returnare a funcțiilor ++ și -
- vor fi diferite. În poziția de prefix, ++ returnează valoarea neincrementată, iar -- returnează
valoarea nedecrementată. Invers, în poziția postfix, ++ returnează valoarea incrementată, iar --
returnează valoarea decrementată.
Pentru a ilustra acest lucru, încercați următorul exemplu, care are ++ și -- atât în poziția prefixă,
cât și în cea postfixă. După cum arată figura 3-7, valorile de returnare ale expresiilor ++ și -- diferă în
pozițiile prefix și postfix, dar nu și noile valori ale membrilor. Acestea sunt întotdeauna incrementate
de ++ sau decrementate de --. Nu uitați să vă opriți și să faceți clic pe Run înainte de fiecare
comentariu, așa cum se explică în prefață.
var aluat = {
făină de patiserie: [ 1 + 3/4, " ceașcă"],
făină de migdale: [1/3, "ceașcă"],
scorțișoară de saigon: [1, "linguriță"],
lămâie tocată măruntZest: [2,
"tsp"], sare de mare: [1/4,
"tsp"],
sifon: [1, "tsp"],
tartru: [1, "tsp"],
iaurt cu vanilie: [1, "cup"],
ou: [1],
WildBlueberries: [1 + 1/4, "ceașcă"]
};
dough.saigonCinnamon[0] ++;
// 1
++ dough.mincedLemonZest[0];
// 3
dough.wildBlueberries[0] --;
67
CAPITOLUL 3 ■ OPERATORI
// 1.25
-- dough.pastryFlour[0];
// .75
aluat.saigonCinnamon[0];
// 2
dough.mincedLemonZest[0];
// 3
aluat.wildBlueberries[0];
// .25
aluat.pastryFloare[0];
// .75
raportul dintre sodă și tartru este de 1:2. Deși tartrul este insipid, sifonul este amar, așa că vrem să ne
asigurăm că rămâne sifon în aluat în urma reacției de fermentare.
var aluat = {
făină de patiserie: [1 + 2/3,
"ceașcă"], făină de alune: [1/3,
"ceașcă"], unt: [3, "tbs"],
zahăr: [2, "tbs"],
seaSalt: [1/4, "tsp"],
sifon: [1/2, "tsp"],
tartru: [1, "tsp"],
cremă de frișcă: [1, "ceașcă"],
coacăze: [1/3, "ceașcă"]
};
Dacă nu sunteți familiarizați cu prepararea scones, combinați ingredientele din aluat astfel:
• Se dă untul rece pe răzătoare în făină și se lucrează cu mixerul de patiserie pentru a obține o făină
omogenă.
• Se bat succesiv crema de frișcă, sucul de portocale, zeama de lămâie tocată mărunt, tartrul și sifonul.
• Coaceți bucățile ușor separate pe o foaie tapetată cu pergament timp de 12 minute la 425°F.
• În cazul în care ambele valori sunt de tip număr și una sau ambele sunt
NaN, se returnează false. În caz contrar, se returnează true dacă numerele
sunt identice și false în caz contrar.
69
CAPITOLUL 3 ■ OPERATORI
Testarea inegalității
În mod frecvent, veți dori să testați inegalitatea, adică o valoare pe care nu doriți ca o expresie să o
returneze. Pentru a face acest lucru, am putea inversa booleanul returnat de === cu operatorul logic not
! ! inversează true în false și false în true. Cu toate acestea, ! are 14 priorități, iar === 9. Pentru a
trunchia 14 cu 9, am înfășura expresia === în operatorul de grupare (), așa cum ilustrează exemplul
următor și figura 3-9.
var aluat = {
făină de patiserie: [1 + 2/3,
"ceașcă"], făină de alune: [1/3,
"ceașcă"], unt: [3, "tbs"],
zahăr: [2, "tbs"],
seaSalt: [1/4, "tsp"],
sifon: [1/2, "tsp"],
tartru: [1, "tsp"],
cremă de frișcă: [1, "ceașcă"],
coacăze: [1/3, "ceașcă"]
};
! (dough.heavyWhippingCream[0] === 2/3);
// adevărat
! (aluat.coacăze[0] === aluat.făină de alune[0]);
// fals
! (dough.hazelnutFlour[0] * 5 === dough.pastryFlour[0]);
// fals
! (dough.soda[0] / dough.tartar[0] === 1);
// adevărat
Figura 3-9. Interogarea JavaScript pentru a afla dacă două expresii nu sunt egale
Ca o scurtătură pentru a compara două expresii pentru egalitate cu === și pentru a inversa verdictul
cu !, JavaScript oferă operatorul !==. !=== parcurge mai întâi protocolul === și apoi face un "nu" logic
asupra verdictului. Astfel, dacă === ar returna adevărat, !== returnează fals, iar dacă === ar returna fals,
!== returnează adevărat. Așadar,
!== este un adevărat contrariat!
71
CAPITOLUL 3 ■ OPERATORI
Important de reținut este că atât === cât și !== trec prin același protocol; !== doar inversează
verdictul. Este ca și cum un judecător te-ar trimite la închisoare când juriul se declară nevinovat și te
lasă liber când juriul se declară vinovat. Nu ar fi ceva?
Să simplificăm exemplul anterior cu !===, verificând munca noastră cu ajutorul figurii 3-10:
var aluat = {
făină de patiserie: [1 + 2/3,
"ceașcă"], făină de alune: [1/3,
"ceașcă"], unt: [3, "tbs"],
zahăr: [2, "tbs"],
seaSalt: [1/4, "tsp"],
sifon: [1/2, "tsp"],
tartru: [1, "tsp"],
cremă de frișcă: [1, "ceașcă"],
coacăze: [1/3, "ceașcă"]
};
dough.heavyWhippingCream[0] !== 2/3;
// adevărat
aluat.coacăze[0] !== aluat.făină de alune[0];
// fals
dough.hazelnutFlour[0] * 5 !== dough.pastryFlour[0];
// fals
dough.soda[0] / dough.tartar[0] !== 1;
// adevărat
din partea mea. JavaScript nu pierde niciodată timp și memorie făcând acest lucru. În plus, dacă
comparați un array cu o funcție, === nu returnează false (sau !=== true), deoarece acestea sunt
subtipuri diferite. Mai degrabă, verdictul boolean derivă pur și simplu din faptul că matricea și funcția
se află în locații diferite în memorie.
Nu uitați că JavaScript stochează valorile de tip șir de caractere, număr, boolean, nedefinit și null într-
un mod diferit față de valorile de tip obiect, matrice și funcție (așa cum am dedus mai devreme în
acest capitol).
Acum nu-ți mai da ochii peste cap. Este vital să înțelegeți acest punct. Așadar, să comparăm
câteva dintre array-urile identice din următorul obiect de aluat reprezentând rețeta pentru o altă
prăjitură preferată a mea, cea cu alune și cireșe, cu === și !== așa în Firebug. După cum arată figura
3-11, === returnează fals și !== returnează adevărat pentru array-uri separate, dar identice.
var aluat = {
făină de patiserie: [1 + 2/3,
"ceașcă"], făină de alune: [1/3,
"ceașcă"], unt: [3, "tbs"],
zahăr: [2, "tbs"],
seaSalt: [1/4, "tsp"],
sifon: [1/2, "tsp"],
tartru: [1, "tsp"],
cremă de frișcă: [1, "ceașcă"],
coacăze: [1/3, "ceașcă"]
};
dough.pastryFlour === [1 + 2/3, "cup"];
// fals
aluat.coacăze !== [1/3, "ceașcă"];
// adevărat
Valorile separate, dar identice ale tipului de obiect sau ale subtipurilor de matrice și funcție nu sunt
niciodată egale. Ca și dumneavoastră și ca și mine, acestea sunt egale doar cu ele însele, după cum
arată exemplul următor și figura 3-12:
var aluat = {
făină de patiserie: [1 + 2/3,
"ceașcă"], făină de alune: [1/3,
"ceașcă"], unt: [3, "tbs"],
zahăr: [2, "tbs"],
seaSalt: [1/4, "tsp"],
sifon: [1/2, "tsp"],
73
CAPITOLUL 3 ■ OPERATORI
Figura 3-12. Un obiect, un tablou sau o funcție este egal doar cu el însuși.
În plus, obiectele, matricele sau funcțiile literale separate, dar identice, nu sunt niciodată egale,
deoarece JavaScript le salvează în locații diferite din memorie. Pentru a ilustra acest aspect, încercați
următorul exemplu în Firebug:
[1 + 2 / 3 , "ceașcă"] === [1 + 2/3, "ceașcă"];
// fals
74
CAPITOLUL 3 ■ OPERATORI
Vom explora mai pe larg compararea prin valoare sau referință în capitolul 5. Este timpul să trecem la
determinarea ordinii relative a numerelor și a șirurilor de caractere cu ajutorul operatorilor > mai mare
și < mai mic.
• Se strecoară 2 2/3 cești de zer apos din 4 cești de iaurt Stonyfield cream-top
pentru a crea 1 1/3 cești de brânză de iaurt.
• Se bat 2 ouă, cu 2/3 cană de brânză de iaurt și 1/3 cană de sirop de arțar pur.
• Pentru a face glazura, bateți până devine cremoasă restul de 2/3 cești de brânză
de iaurt cu 1 1/3 linguriță (4 lingurițe) de sirop de arțar pur și 2 lingurițe de nuci
pecan măcinate.
75
CAPITOLUL 3 ■ OPERATORI
Figura 3-13. Determinarea faptului dacă un număr este mai mare decât altul cu ajutorul operatorului >
În plus față de compararea numerelor, > este uneori utilizat pentru a compara șiruri de caractere.
Cu toate acestea, ea face acest lucru numeric prin codificarea Unicode pentru fiecare caracter. Literele
majuscule sunt mai mari decât caracterele minuscule. Prin urmare, doriți să invocați toLowerCase() pe
ambii operanzi pentru a obține o comparație alfabetică, așa cum afișează Lista 3-1. Rețineți că am
explorat toLowerCase() și alte metode pentru șiruri de caractere în Capitolul 2.
Listarea 3-1. Compararea șirurilor de caractere în ordine alfabetică cu ajutorul operatorului >
"măr" > "Granny Smith";
// fals
"măr".toLowerCase() > "Granny Smith".toLowerCase();
// adevărat
Determinarea dacă un număr sau un șir de caractere este mai mic decât
altul
Dar dacă vrem să facem invers? Cu alte cuvinte, vrem să determinăm dacă în aluat există mai puțin
sirop de arțar pur decât nuci pecan măcinate.
Hmm.
Vrei să ghicești?
Da, întoarceți > și veți avea operatorul < less than, care vă spune dacă primul său operand este
mai mic decât al doilea operand. Haideți să ne jucăm cu < în Firebug, verificând munca noastră cu
ajutorul figurii 3-14.
var cake = {
aluat: {
organicPastryFloar: [1 + 1/2, "cup"],
freshlyGroundNutmeg: [1/4, "tsp"],
77
CAPITOLUL 3 ■ OPERATORI
Figura 3-14. Determinarea faptului dacă un număr este mai mic decât altul cu ajutorul operatorului >
78
CAPITOLUL 3 ■ OPERATORI
var cake = {
aluat: {
făină de patiserie organică: [ 1 + 1/2,
"ceașcă"], nucșoară proaspăt
măcinată: [1/4, "linguriță"],
scorțișoară saigon: [1/2, "linguriță"],
sifon: [1, "tsp"],
tartru: [1, "tsp"],
ou: [2],
yogurtCheese: [2/3, "cup"],
pureMapleSyrup: [1/3, "ceașcă"],
mărunțitGrannySmith: [1 + 2/3, "ceașcă"],
tocatPecans: [1/2, "ceașcă"]
},
glazură: {
yogurtCheese: [2/3, "cup"],
pureMapleSyrup: [1 + 1/3, "tbs"], nuci
de pădure măcinate: [2, "tsp"]
}
};
! (cake.icing.yogurtCheese[0] > cake.dough.yogurtCheese[0]);
// adevărat
! (cake.icing.yogurtCheese[0] < cake.dough.yogurtCheese[0]);
// adevărat
79
CAPITOLUL 3 ■ OPERATORI
La fel cum JavaScript oferă !== ca o scurtătură pentru a inversa verdictul boolean al lui === cu !, el oferă și
>= ca o scurtătură pentru a inversa verdictul boolean al lui < cu ! și <= ca o scurtătură pentru a inversa
verdictul boolean al lui > cu !. Rețineți că nici >=, nici <= nu testează egalitatea cu operatorul ===. Mai
degrabă,
>= efectuează o operație "nu mai mică decât", în timp ce <= efectuează o operație "nu mai mare
decât". Încercați să comparați câteva ingrediente cu >= și <=, verificând munca dvs. Figura 3-16:
var cake = {
aluat: {
făină de patiserie organică: [ 1 + 1/2,
"ceașcă"], nucșoară proaspăt
măcinată: [1/4, "linguriță"],
scorțișoară saigon: [1/2, "linguriță"],
sifon: [1, "tsp"],
tartru: [1, "tsp"],
ou: [2],
yogurtCheese: [2/3, "cup"],
pureMapleSyrup: [1/3, "ceașcă"],
mărunțitGrannySmith: [1 + 2/3, "ceașcă"],
tocatPecans: [1/2, "ceașcă"]
},
glazură: {
yogurtCheese: [2/3, "cup"],
pureMapleSyrup: [1 + 1/3, "tbs"], nuci
de pădure măcinate: [2, "tsp"]
}
};
cake.icing.yogurtCheese[0] <= cake.dough.yogurtCheese[0];
// adevărat
cake.icing.yogurtCheese[0] >= cake.dough.yogurtCheese[0];
// adevărat
80
CAPITOLUL 3 ■ OPERATORI
Rețineți că >, <, >= și <= pot compara numai numere sau șiruri de caractere, astfel încât
JavaScript convertește operanzii de alte tipuri de valori după cum urmează:
• Convertiți obiectele în numere, dacă este posibil. În caz contrar, convertiți-le în șiruri de caractere.
• Dacă ambii operanzi sunt acum șiruri de caractere, comparați-le în funcție de codificarea lor
Unicode.
• Convertește orice operand string, boolean, null sau nedefinit într-un număr, adică
true în 1, false în 0, null în 0 și undefined în NaN, iar șirurile de caractere într-un
număr sau NaN. Ambii operanzi vor fi acum numere, deci comparați-le
matematic, cu excepția cazului în care unul sau ambii sunt NaN, caz în care se
returnează false indiferent de situație.
Astfel, dacă unul sau ambii operanzi sunt de tip număr, boolean, null sau valoare nedefinită,
atunci >, <, >= și <= vor compara întotdeauna operanzii din punct de vedere matematic. Altfel spus,
compararea șirurilor de caractere se face numai atunci când ambii operanzi sunt șiruri de caractere sau
obiecte care nu pot fi convertite în numere.
81
CAPITOLUL 3 ■ OPERATORI
Loganberries. Rețineți, de asemenea, că Brown Cow este o filială a Stonyfield, așa că iaurturile lor cu
cremă au un gust destul de asemănător.
Următorul obiect brioșă reprezintă rețeta de brioșe cu mure. Făina de ovăz și de orz o cumpăr de pe
www.kingarthurflour.com. Totuși, le puteți înlocui cu făina de patiserie integrală organică Bob's Red Mill
cu care am făcut aluatul. Pentru a face brioșele, veți face următoarele:
1. Se cerne făina de ovăz, făina de orz, făina de patiserie, zahărul,
nucșoara proaspăt măcinată, scorțișoara de Saigon și sarea de mare.
Modul ciudat în care || și && își aleg valoarea de retur reprezintă baza algebrei booleene. Termen
înfricoșător, dar nu trebuie să vă faceți griji. Algebra cu booleeni este mai simplă decât cu numere.
Iată cum funcționează.
• Valoarea de retur pentru || se va converti la true dacă primul sau al doilea operand
sau ambele se evaluează sau se convertesc la true. În caz contrar, valoarea de
returnare pentru || se va converti în fals.
Spunând sau cu ||
În măsura în care ===, !==, >, <, <, >= și <= returnează un verdict boolean și || returnează unul dintre
operanzi, puteți utiliza || pentru a face algebră booleană pe două expresii de comparație. Dacă una
dintre cele două comparații returnează adevărat sau ambele returnează adevărat, || va returna
adevărat. Altfel spus, || va returna fals numai dacă ambele comparații returnează fals. Așadar, putem
testa dacă una sau ambele comparații sunt valide, astfel. Rețineți că este posibil să adăugați o nouă
linie între un operator binar precum || și al doilea operand al acestuia. Asigurați-vă că faceți clic pe
Run înainte de fiecare comentariu. Deci, de patru ori în total. Apoi verificați-vă munca cu ajutorul
figurii 3-17.
var brioșă = {
făină de ovăz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de orz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de patiserie:
[1 + 1/3, "ceașcă"], nucșoară
proaspăt măcinată: [1/4, "linguriță"],
scorțișoară saigon: [1/2,
" l i n g u r i ț ă "], sare de mare:
[1/4, "linguriță"],
sifon: [1, "tsp"],
tartar: [1, "tsp"], mapleBrownCow:
[1 + 1/2, "ceașcă"], boysenberries:
[2, "ceașcă"], boabe de păstăi
tocate: [1/3, "ceașcă"]
};
muffin.mapleBrownCow[0] > m u f f i n . boysenberries[0] ||
muffin.oatFlour[0] === muffin.barleyFlour[0];
// adevărat
muffin.oatFlour[0] === m u f f i n .barleyFlour[0] ||
muffin.mapleBrownCow[0] > muffin.boysenberries[0];
// adevărat
muffin.boysenberries[0] > m u f f i n . choppedPecans[0] ||
muffin.pastryFlour[0] > muffin.barleyFlour[0];
// adevărat
muffin.boysenberries[0] < m u f f i n . choppedPecans[0] ||
muffin.pastryFlour[0] < muffin.barleyFlour[0];
// fals
83
CAPITOLUL 3 ■ OPERATORI
Figura 3-17. || va returna adevărat dacă cel puțin una dintre cele două comparații este validă.
Figura 3-18. && va returna adevărat numai dacă ambele comparații sunt valide.
Încatenare || Expresii
Dacă înlănțuiți două expresii ||, puteți testa dacă una dintre cele trei comparații este validă. Încercați
acest lucru în Firebug, verificând munca dvs. cu ajutorul figurii 3-19. Rețineți că ||| are asociativitate
L și că JavaScript nu evaluează al doilea operand atunci când primul operand evaluează sau se
convertește la true. Astfel, în exemplul următor, deoarece prima comparație este adevărată, JavaScript
nu evaluează muffin.oatFlour !== muffin.barleyFlour sau muffin.pastryFlour < muffin.barleyFlour.
var brioșă = {
făină de ovăz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de orz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de patiserie:
[1 + 1/3, "ceașcă"], nucșoară
proaspăt măcinată: [1/4, "linguriță"],
scorțișoară saigon: [1/2,
" l i n g u r i ț ă "], sare de mare:
[1/4, "linguriță"],
sifon: [1, "tsp"],
tartar: [1, "tsp"], mapleBrownCow:
[1 + 1/2, "ceașcă"], boysenberries:
[2, "ceașcă"], boabe de păstăi
tocate: [1/3, "ceașcă"]
};
muffin.mapleBrownCow[0] > m u f f i n . boysenberries[0] ||
muffin.oatFlour[0] !== m u f f i n . barleyFlour[0] | |
muffin.pastryFlour[0] < muffin.barleyFlour[0];
// adevărat
85
3
CAPITOLUL 3 ■ OPERATORI
Figura 3-19. Determinarea faptului că cel puțin una dintre cele trei comparații este adevărată
După cum v-ați putea imagina, puteți continua, înlănțuind oricâte operații || doriți. Astfel, în
următorul exemplu, testăm dacă cel puțin una dintre cele cinci comparații este validă. Astfel, după cum
arată figura 3-20, chiar dacă doar a doua și a patra comparație sunt valide, adică returnează adevărat, în
general expresiile || înlănțuite returnează adevărat:
var brioșă = {
făină de ovăz: [ 1 / 3 ,
" c e a ș c ă " ] , f ă i n ă de orz:
[ 1 / 3 , " c e a ș c ă " ] , făină de
patiserie: [1 + 1/3, "ceașcă"],
nucșoară proaspăt măcinată: [1/4,
"linguriță"], scorțișoară saigon:
[1/2, " l i n g u r i ț ă "], sare de
mare: [1/4, "linguriță"],
sifon: [1, "tsp"],
tartar: [1, "tsp"],
mapleBrownCow: [1 + 1/2,
"ceașcă"], boysenberries: [2,
"ceașcă"], boabe de păstăi
tocate: [1/3, "ceașcă"]
};
muffin.mapleBrownCow[0] > m u f f i n . boysenberries[0] ||
muffin.oatFlour[0] !== muffin.barleyFlour[0] ||
muffin.freshlyGroundNutmeg[0] >= muffin.saigonCinnamon[0] | |
m u f f i n . choppedPecans[0] <= m u f f i n . mapleBrownCow[0] ||
muffin.pastryFlour[0] === muffin.barleyFlour[0];
// adevărat
86
CAPITOLUL 3 ■ OPERATORI
Figura 3-20. Determinarea faptului dacă cel puțin una dintre cele cinci comparații este adevărată
Figura 3-21. Determinarea faptului dacă toate cele cinci comparații sunt adevărate
Haideți să facem && fericit și să schimbăm a treia comparație "nu mai puțin decât" în "mai puțin
decât". După cum arată figura 3-22, deoarece toate cele cinci comparații sunt adevărate, în general lanțul
&& returnează adevărat. Ura!
var brioșă = {
făină de ovăz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de orz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de patiserie:
[1 + 1/3, "ceașcă"], nucșoară
proaspăt măcinată: [1/4, "linguriță"],
scorțișoară saigon: [1/2,
" l i n g u r i ț ă "], sare de mare:
[1/4, "linguriță"],
sifon: [1, "tsp"],
tartar: [1, "tsp"], mapleBrownCow:
[1 + 1/2, "ceașcă"], boysenberries:
[2, "ceașcă"], boabe de păstăi
tocate: [1/3, "ceașcă"]
};
muffin.mapleBrownCow[0] < m u f f i n . boysenberries[0] &&
muffin.oatFlour[0] === muffin.barleyFlour[0] &&
m u f f i n .freshlyGroundNutmeg[0] < muffin.saigonCinnamon[0] &&
m u f f i n . choppedPecans[0] <= m u f f i n . mapleBrownCow[0] &&
muffin.pastryFlour[0] > muffin.barleyFlour[0];
// adevărat
88
CAPITOLUL 3 ■ OPERATORI
Figura 3-22. Deoarece toate cele cinci comparații sunt adevărate, în general, lanțul && returnează adevărat.
Figura 3-23. Încatenarea expresiilor && și ||| pentru a efectua o comparație complexă
Pentru a ilustra mai bine evaluarea leneșă a lui JavaScript pentru || și &&, să înlocuim comparația
finală cu un apel alert() care ar spune "Nu intrați în panică!". Acum faceți clic pe Run în Firebug.
Firebug nu deschide o casetă de dialog de alertă, deoarece JavaScript nu s-a deranjat niciodată să
invoce alert().
var brioșă = {
făină de ovăz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de orz: [ 1 / 3 ,
" c e a ș c ă " ] , făină de patiserie:
[1 + 1/3, "ceașcă"], nucșoară
proaspăt măcinată: [1/4, "linguriță"],
scorțișoară saigon: [1/2,
" l i n g u r i ț ă "], sare de mare:
[1/4, "linguriță"],
sifon: [1, "tsp"],
tartar: [1, "tsp"], mapleBrownCow:
[1 + 1/2, "ceașcă"], boysenberries:
[2, "ceașcă"], boabe de păstăi
tocate: [1/3, "ceașcă"]
};
muffin.mapleBrownCow[0] < m u f f i n . boysenberries[0] &&
muffin.oatFlour[0] === muffin.barleyFlour[0] ||
muffin.freshlyGroundNutmeg[0] < m u f f i n . saigonCinnamon[0] &&
muffin.choppedPecans[0] <= muffin.mapleBrownCow[0] || alert("Nu
intrați în panică!");
// adevărat
Cred că brioșele noastre cu boboci și mure maro sunt gata, așa că hai să le scoatem din cuptor.
Mmmh, împărțiți și bucurați-vă.
90
CAPITOLUL 3 ■ OPERATORI
valoarea de returnare bazată pe booleanul pe care îl evaluează sau îl convertește primul său operand.
Dacă primul operand se evaluează sau se convertește la true:
• || returnează primul său operand.
• &&& returnează al doilea operand.
• ?: returnează al doilea operand.
Pe de altă parte, dacă primul operand se evaluează sau se convertește la fals:
Figura 3-24. Alegerea condiționată a cantității de lapte de cultură cu ajutorul operatorului ?:.
Figura 3-25. Efectuarea a două expresii ca fiind una singură cu ajutorul operatorului ,
var aluat = {
hardWhiteWhiteWholeWheatFloare: [2,
" ceașcă"], zahăr: [1/3, "ceașcă"],
94
CAPITOLUL 3 ■ OPERATORI
Figura 3-26. Ștergerea unui membru cu delete și verificarea dispariției acestuia cu typeof și in
Rezumat
Pe lângă interogarea obiectelor sau a tablourilor, invocarea funcțiilor, calculele matematice, lipirea
șirurilor de caractere și atribuirea de valori, operatorii oferă o modalitate de a verifica valorile de
returnare pentru expresii. Un lucru bun - în afară de literali, expresiile JavaScript tind să evalueze la
valori diferite în funcție de ceea ce face un vizitator sau de browserul cu care o face. Așadar, există
mulți operatori care ajută la verificarea valorilor de returnare. Aceștia se vor dovedi de neprețuit în
Capitolul 4, unde vom explora controlul fluxului, și în capitolele DOM, unde vom testa caracteristicile
înainte de a încerca să le folosim.
95
CHAPTER 4
■■■
Controlul fluxului
Mergând pe un traseu forestier în luna mai, am surprins o căprioară și puiul ei nou-născut. Căprioara a
scos un sforăit răgușit pentru a avertiza puiul de pericol. Neștiind ce să facă, cățelul s-a clătinat spre
mine și s-a așezat între picioarele mele. Tremurând de frică, s-a uitat la mine și a behăit slab,
implorându-mă să îl țin în siguranță. Căprioara stătea la 20 de metri distanță, tremurând de agitație.
Evident, voia ca puiul de cerb să fugă de prădător, nu spre el.
JavaScript este ca un căprior nou-născut în sensul că nu știe în ce direcție vreți să alerge. Deci, în
mod implicit, va rula pur și simplu înainte, adică de la prima linie din scriptul dumneavoastră până la
ultima. Cu toate acestea, există patru moduri de a manipula acest flux secvențial, fără minte.
În primul rând, puteți trimite JavaScript pe căi diferite cu ajutorul instrucțiunilor if și switch.
Acestea sunt denumite instrucțiuni condiționale deoarece căile se execută condiționat în raport cu
valoarea booleană a unei expresii. true îi dă undă verde lui JavaScript să urmeze o cale, în timp ce false
îi spune lui JavaScript fie să nu facă nimic, fie să urmeze o cale de cădere. În al doilea rând, îi puteți
spune lui JavaScript să parcurgă mai multe tururi ale unei căi de buclă cu una dintre cele patru
instrucțiuni de buclă: while, do while, for, for sau in. La fel ca și if și switch, buclele se execută
condiționat în raport cu o expresie booleană: true îi spune lui JavaScript să ia un alt sens giratoriu, în
timp ce false îi spune lui JavaScript să nu o facă. În al treilea rând, puteți întrerupe fluxul cu o
instrucțiune perturbatoare, cum ar fi break, continue sau return. Aceste instrucțiuni împiedică
JavaScript să își continue drumul.
În al patrulea rând, puteți sări temporar în altă parte a unui script prin intermediul invocării unei
funcții. Prin aceasta, vreau să spun că JavaScript pleacă și execută funcția și apoi se întoarce în locul
de unde ați invocat-o.
Nici declarațiile disruptive, nici invocările de funcții nu sunt dinamice. Altfel spus, niciuna dintre
ele nu oferă o modalitate prin care JavaScript să ia o decizie în funcție de circumstanțe. Așadar, cu ele,
un cerb ar trebui să fugă de veverițe, dar și de lupi. Pe de altă parte, declarațiile condiționale și de
buclă sunt dinamice, deci oferă o modalitate prin care JavaScript poate gândi înainte de a sări.
Și atunci, cum gândește JavaScript? Am făcut aluzie la acest aspect mai devreme, dar răspunsul este
simplu: expresii booleene. Expresiile veridice, cele care returnează true sau care pot fi convertite în true,
sunt o lumină v e r d e , î n timp ce expresiile false, cele care returnează undefined, null, "", 0, NaN sau false,
sunt o lumină roșie. Așadar, nu este surprinzător faptul că fiecare instrucțiune condițională sau de buclă
conține o expresie booleană, care permite JavaScript să ia o decizie.
Ce altceva conțin enunțurile condiționate sau în buclă? Acestea conțin trasee sub formă de
instrucțiuni copil sau blocuri, care sunt instrucțiuni înfășurate în paranteze curbe. Din acest motiv,
instrucțiunile condiționate și de buclă sunt denumite instrucțiuni compuse. Așadar, dacă doriți ca
JavaScript să gândească, scrieți o instrucțiune compusă.
Problema este că sintaxa formală JavaScript limitează o instrucțiune compusă la o instrucțiune
copil. De exemplu, o instrucțiune condițională if, pe care o vom analiza imediat, poate avea doar un
singur copil
97
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
după expresia booleană. În următoarea instrucțiune if, run(miles); este singura instrucțiune copil
permisă de JavaScript:
if (timeToRun === true) run(miles);
De multe ori, acest lucru nu este suficient, iar JavaScript știe acest lucru. Dacă grupați mai multe
instrucțiuni copil într-o pereche de acolade, JavaScript va privi în altă parte și va considera acest grup,
denumit bloc, ca fiind o singură instrucțiune copil. Așadar, dacă vreau ca JavaScript să ruleze trei
instrucțiuni copil ori de câte ori este timpul să ruleze, adică timeToRun conține true, atunci pot grupa
aceste instrucțiuni într-un bloc. JavaScript va fi fericit ca o scoică, iar eu voi alerga încălțat și nu desculț:
if (timeToRun === true) {
lace(shoes); run(miles);
duș();
}
Rețineți că blocul de declarații copil nu este urmat de un punct și virgulă. Cu toate acestea, blocul copil
din cadrul blocului sunt.
Faceți clic pe Clear în ambele panouri Firebug și apoi încercați să apelați cealaltă metodă,
mayfly.kmLeftToLive(). Aceasta nu acceptă niciun parametru. Așadar, introduceți următorul cod și
faceți clic pe Run pentru a afla câți kilometri au mai rămas la perechea noastră de pantofi Mayfly.
Vedem o ieșire deoarece variabila tally este mai mică de 100 și, prin urmare, condiția din al doilea
if este adevărată.
99
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
mayfly.kmLeftToLive();
// "Mayfly mai are 60 de kilometri de trăit".
Astfel, tally < 100 returnează false, iar JavaScript merge pe calea else. Prin urmare,
mayfly.kmLeftToLive() returnează "Mayfly is dead!" pentru a indica faptul că este timpul să cumpărați un nou
Mayfly.
Apropo, chiar dacă nu sunteți un alergător, ați putea dori să încercați Mayfly cândva. Partea
superioară este de un portocaliu strălucitor, cu o grilă de susținere neagră care seamănă cu aripa unei
muște - va fi greu de ratat cu o astfel de pereche!
Pe de altă parte, dacă înfășurați toate declarațiile copil în acolade, s-ar putea să vă pierdeți timpul
cu depanarea scripturilor care au, să zicem, 143 de acolade de deschidere, dar numai 138 de acolade de
închidere. Sau s-ar putea să constatați că dacă adăugați bretele opționale în scripturile dvs., acestea
devin mai puțin lizibile.
Indiferent dacă înfășurați sau nu declarațiile single child în paranteze curly braces, important de
reținut este că ambele stiluri sunt corecte. Lui JavaScript nu-i pasă de stilul pe care îl alegeți. Mai mult,
chiar și programatorii care înfășoară totul în acolade le omit pentru a folosi idiomul else if. (Totuși,
acest lucru se datorează probabil faptului că ei cred că else if este o declarație, nu un idiom. Așadar,
nici măcar nu știu că își încalcă mantra)!
102
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
if (fridge.brownCow) {
smoothie = "Iaurt cu cremă de vacă maro";
} else if (fridge.stonyfield) {
smoothie = "Stonyfield cream-top yogurt";
} else if (fridge.fage) {
smoothie = "Cremă cultivată Fage";
} else if (fridge.lifeway) {
smoothie = "Lifeway Greek-style kefir";
}
smoothie += ", smântână și lapte hrănite cu iarbă, scorțișoară Saigon și afine sălbatice."
// "Iaurt cu cremă Brown Cow, smântână și lapte din iarbă, scorțișoară Saigon,
// și afine sălbatice."
Verificați munca dvs. cu ajutorul figurii 4-2.
Este mult mai simplu de codat și de citit, nu credeți? Acum, ce se întâmplă aici? Pe scurt, din
moment ce toate clauzele else conțin o singură instrucțiune copil, acoladele sunt opționale, așa că le-
am omis împreună cu întreruperile de linie. Procedând astfel, cuvintele-cheie else și if se unesc, motiv
pentru care expresia este denumită else if.
Dar există o problemă: dacă niciuna dintre expresiile booleene nu returnează true, JavaScript nu
are nicio cale de urmat. Putem remedia această problemă? Sigur că da. Pur și simplu adăugați o clauză
else la condiția finală "if" imbricata. Dar de data aceasta, folosiți bretelele ondulate opționale. Haideți
să facem Dannon (Danone în Regatul Unit) implicit și apoi să setăm toți membrii frigiderului la false,
astfel încât să putem testa calea de trecere prin cădere, ca în figura 4-3:
var fridge = {
brownCow: false,
stonyfield:
f a l s e , fage:
f a l s e , lifeway:
false
};
104
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
var smoothie;
if (fridge.brownCow) {
smoothie = "Iaurt cu cremă de vacă maro";
} else if (fridge.stonyfield) {
smoothie = "Stonyfield cream-top yogurt";
} else if (fridge.fage) {
smoothie = "Cremă cultivată Fage";
} else if (fridge.lifeway) {
smoothie = "Lifeway Greek-style kefir";
} else {
smoothie = "iaurt Dannon";
}
smoothie += ", smântână și lapte hrănite cu iarbă, scorțișoară Saigon și afine sălbatice."
// "Iaurt Dannon, smântână și lapte din iarbă, scorțișoară Saigon și afine sălbatice."
Deci, asta este. JavaScript are cinci căi din care să aleagă. Înainte de a trece mai departe, rețineți că,
deoarece toate cele cinci căi sunt declarații cu un singur copil, putem omite acoladele:
var fridge = {
brownCow: false,
stonyfield:
f a l s e , fage:
f a l s e , lifeway:
false
};
var smoothie;
if (fridge.brownCow)
smoothie = "Brown Cow cream-top yogurt";
else if (fridge.stonyfield)
smoothie = "Stonyfield cream-top yogurt"; else
if (fridge.fage)
105
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
■ Notă Chiar dacă puteți crea o instrucțiune de expresie prin simpla atașare a unei cozi de punct și virgulă la orice
expresie, în general faceți acest lucru numai pentru expresiile de atribuire, invocare, incrementare sau
descreștere.
Faceți clic pe Clear în ambele panouri Firebug și rescrieți exemplul else if folosind expresii
condiționale imbricate, astfel:
var fridge = {
brownCow: true,
stonyfield: false,
fage: true,
lifeway: false
};
var smoothie = fridge.brownCow ? "Brown Cow cream-top yogurt" :
(fridge.stonyfield ? "Stonyfield cream-top yogurt" :
(fridge.fage ? "Cremă de cultură Fage" :
(fridge.lifeway ? "Lifeway Greek-style kefir" : "Dannon yogurt"))));
smoothie += ", smântână și lapte hrănite cu iarbă, scorțișoară Saigon și afine sălbatice."
// "Iaurt cu cremă Brown Cow, smântână și lapte din iarbă, scorțișoară Saigon,
// și afine sălbatice."
Verificați munca dvs. cu ajutorul figurii 4-4.
106
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Din motive de lizibilitate, recomand să nu se înglobeze mai mult de o expresie ?:. Așadar, ceea ce
am făcut mai devreme nu este recomandat. Cu toate acestea, veți întâlni astfel de trucuri în scripturi
scrise de alții, iar familiarizarea cu această tehnică se va dovedi utilă.
107
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
pauză;
cazul 10:
name = "Holmes";
break;
cazul 17:
name = "Wallace";
break;
cazul 34:
name = "Mendenhall";
break;
cazul 43:
name = "Polamalu";
break;
cazul 83:
name = "Miller";
break;
cazul 86:
name = "Ward";
break;
cazul 92:
name = "Harrison";
break;
cazul 94:
name = "Timmons";
break;
cazul 96:
name = "Hood";
break;
implicit:
name = "nu este purtat de niciun Steeler";
break;
}
"Numărul " + jersey + " este " + nume + " .".";
// "Numărul 34 este Mendenhall."
Verificați munca dvs. cu figura 4-5.
108
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Figura 4-5. Codificarea unei ramificații cu mai multe căi cu o instrucțiune switch
Aici, JavaScript a trebuit să evalueze primele patru clauze de caz pentru a identifica 34 ca fiind
Mendenhall. Acum, schimbați jersey cu un număr pe care nicio expresie de caz nu îl potrivește
pentru a vă asigura că traseul implicit funcționează:
var jersey = 1, name = "";
switch (jersey) {
cazul 7:
name = "Roethlisberger";
break;
cazul 10:
name = "Holmes";
break;
cazul 17:
name = "Wallace";
break;
cazul 34:
name = "Mendenhall";
break;
cazul 43:
name = "Polamalu";
109
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
pauză;
cazul 83:
name = "Miller";
break;
cazul 86:
name = "Ward";
break;
cazul 92:
name = "Harrison";
break;
cazul 94:
name = "Timmons";
break;
cazul 96:
name = "Hood";
break;
implicit:
name = "nu este purtat de niciun Steeler";
break;
}
"Numărul " + jersey + " este " + nume + " .".";
// "Numărul 1 nu este purtat de niciun Steeler."
Deoarece nu există o clauză de caz pentru 1, JavaScript a rulat calea implicită. Rețineți că, deși calea
implicită
este de obicei ultimul caz, dar JavaScript nu necesită acest lucru. Așa că hai să o punem pe primul loc:
var jersey = 1, name = "";
switch (jersey) {
implicit:
name = "nu este purtat de niciun Steeler";
break;
cazul 7:
name = "Roethlisberger";
break;
cazul 10:
name = "Holmes";
break;
cazul 17:
name = "Wallace";
break;
cazul 34:
name = "Mendenhall";
break;
cazul 43:
name = "Polamalu";
break;
cazul 83:
name = "Miller";
break;
cazul 86:
name = "Ward";
break;
cazul 92:
name = "Harrison";
110
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
pauză;
cazul 94:
name = "Timmons";
break;
cazul 96:
name = "Hood";
break;
}
"Numărul " + jersey + " este " + nume + " .".";
// "Numărul 1 nu este purtat de niciun Steeler."
Funcționează la fel de bine și acolo. Acum, după cum am menționat mai devreme, clauzele case
pot avea mai multe expresii case. Acest lucru vă oferă o modalitate de a rula o cale pentru mai mult de
un șir sau număr. De exemplu, numerele 92 și 97 de la Steelers se numesc amândouă Harrison, așa că
hai să omorâm doi iepuri dintr-o lovitură:
var jersey = 92, name = "";
switch (jersey) {
cazul 7:
name = "Roethlisberger";
break;
cazul 10:
name = "Holmes";
break;
cazul 17:
name = "Wallace";
break;
cazul 34:
name = "Mendenhall";
break;
cazul 43:
name = "Polamalu";
break;
cazul 83:
name = "Miller";
break;
cazul 86:
name = "Ward";
break;
cazul 92:
cazul 97:
name = "Harrison";
break;
cazul 94:
name = "Timmons";
break;
cazul 96:
name = "Hood";
break;
implicit:
name = "nu este purtat de niciun Steeler";
break;
}
"Numărul " + jersey + " este " + nume + " .".";
111
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
JavaScript a căzut de la clauza de caz pentru 92 la cea pentru 97. Acum, să fim mai îndrăzneți, să omitem
câteva
break și vedeți ce se întâmplă:
var jersey = 7, name = "";
switch (jersey) {
cazul 7:
name = "Roethlisberger";
cazul 10:
name = "Holmes";
cazul 17:
name = "Wallace";
cazul 34:
name = "Mendenhall";
cazul 43:
name = "Polamalu";
cazul 83:
112
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
name = "Miller";
break;
cazul 86:
name = "Ward";
break;
cazul 92:
cazul 97:
name = "Harrison";
break;
cazul 94:
name = "Timmons";
break;
cazul 96:
name = "Hood";
break;
implicit:
name = "nu este purtat de niciun Steeler";
break;
}
"Numărul " + jersey + " este " + nume + " .".";
// "Numărul 7 este Miller."
Aici, JavaScript începe să ruleze blocul de comutare cu instrucțiunea name = "Roethlisberger"; și
se oprește când întâlnește instrucțiunea break după instrucțiunea name = "Miller"; astfel încât, pentru
un număr de tricou de 7, JavaScript returnează incorect "Miller". Altfel spus, JavaScript tocmai a rulat
un traseu ca cel pentru următoarea condiție if ridicolă, care suprascrie numele de șase ori la rând!
if (jersey === 7) {
nume = "Roethlisberger";
nume = "Holmes";
nume = " Wallace";
nume = "Mendenhall";
nume = " Polamalu";
nume = "Miller";
}
Acum, puneți declarațiile de întrerupere înapoi, schimbați jersey în 96 și ștergeți întreruperea de după
clauza case
pentru 96. Faceți clic pe Run pentru a vedea ce se întâmplă:
var jersey = 96, name = "";
switch (jersey) {
cazul 7:
name = "Roethlisberger";
break;
cazul 10:
name = "Holmes";
break;
cazul 17:
name = "Wallace";
break;
cazul 34:
name = "Mendenhall";
break;
cazul 43:
name = "Polamalu";
113
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
pauză;
cazul 83:
name = "Miller";
break;
cazul 86:
name = "Ward";
break;
cazul 92:
cazul 97:
name = "Harrison";
break;
cazul 94:
name = "Timmons";
break;
cazul 96:
name = "Hood";
implicit:
name = "nu este purtat de niciun Steeler";
break;
}
"Numărul " + jersey + " este " + nume + " .".";
// "Numărul 96 nu este purtat de niciun Steeler."
După cum puteți vedea, JavaScript va continua să ruleze instrucțiuni, chiar și cele din clauza
implicită, până când va întâlni fie o instrucțiune perturbatoare, fie o acoladă de închidere. Neglijând să
punem o instrucțiune break după clauza case pentru 96, am făcut ca JavaScript să ruleze efectiv
următoarea condiție if:
if (jersey === 96) {
name = "Hood";
name = "nu este purtat de niciun Steeler";
}
Rețineți că dacă am fi pus cazul implicit în partea de sus a comutatorului, JavaScript nu ar fi căzut
prin intermediul clauzei de caz pentru 96 la implicit.
După cum s-a menționat anterior, dacă un comutator apare în cadrul unei funcții, atunci puteți
încheia căile cu o declarație disruptivă return în loc de break. Adesea, instrucțiunea return nu numai
că marchează sfârșitul traseului, dar este și traseul în sine. Așadar, haideți să mergem mai departe și
să punem comutatorul nostru într-o funcție, astfel încât să putem folosi declarațiile return:
var jersey = 7, name = "";
function identifyPlayer() {
switch (jersey) {
cazul 7:
return "Roethlisberger";
cazul 10:
return "Holmes";
cazul 17:
return "Wallace";
cazul 34:
return "Mendenhall";
cazul 43:
return "Polamalu";
cazul 83:
return "Miller";
cazul 86:
114
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
return "Ward";
caz 92:
return "Harrison";
cazul 94:
return "Timmons";
cazul 96:
return "Hood";
default:
returnează "nu este purtat de niciun Steeler";
}
}
"Numărul " + jersey + " este " + identifyPlayer() + ".";
// "Numărul 7 este Roethlisberger."
Verificați munca dvs. cu ajutorul figurii 4-7.
Figura 4-7. În cadrul unei funcții, puteți înlocui instrucțiunile break cu instrucțiuni return.
O ultimă observație cu privire la declarațiile switch: expresiile case, care se află între cuvântul
cheie case și două puncte, sunt de obicei literali de tip șir sau număr. Cu toate acestea, orice expresie
este suficientă. Asigurați-vă doar că acestea nu fac altceva decât să returneze o valoare pentru
operatorul === pentru a testa identitatea față de valoarea expresiei de comutare. Spun acest lucru
deoarece JavaScript nu evaluează expresiile de tip "case" în aval de cea care corespunde expresiei de
comutare. Așadar, nu știți niciodată câte dintre expresiile case vor fi executate. De exemplu, dacă a
patra expresie de caz invocă o funcție care returnează un număr cu care să lucreze ===, dar modifică și
trei variabile în altă parte în scriptul dumneavoastră, iar a doua expresie de caz se potrivește cu expresia
de comutare, atunci JavaScript nu are niciodată șansa de a modifica acele trei variabile. Această
imprevizibilitate este motivul pentru care scrierea expresiilor de caz cu efecte secundare nu este privită
cu ochi buni. Nu o faceți.
115
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
116
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
}
i ++;
}
"Mă simt " + starea de spirit + " !";
// "Mă simt veselă!"
Verificați munca dvs. cu ajutorul figurii 4-8.
Aici avem o matrice looseLeafTea cu nouă elemente. Înainte de a rula bucla while, inițializăm o
variabilă de buclă numită i la 0, indexul primului element din looseLeafTea. Pentru expresia booleană a
buclei while, testăm dacă i este mai mică decât looseLeafTea.length, care este 9. La sfârșitul
parcursului while, adăugăm 1 la i cu ajutorul operatorului ++. În acest fel, bucla se va executa de cel
mult nouă ori, câte o iterație pentru fiecare element din looseLeafTea.
În timpul unui anumit ocol al traseului while, putem interoga următorul element din looseLeafTea
cu i și operatorul []. Astfel, de exemplu, în timpul celei de-a patra iterații, i ar fi 3 (nu uitați că a
început de la 0) și, prin urmare, looseLeafTea[i] ar fi "Keemun". Acest comportament este tipic pentru o
buclă. Adică, la fiecare rundă, JavaScript trebuie să execute același set de comenzi pe o variabilă,
membru sau element diferit. Așadar, buclele oferă o modalitate de a face lucrurile în lot. Este ca și cum
ai coace fursecuri cu fulgi de ovăz!
Acum, dacă nu-i spunem altfel lui JavaScript, va trebui să ia toate cele nouă sensuri giratorii ale
traseului. Nu este nimic rău în asta, dar este ineficient. În cazul în care un element conține "Borpatra",
atunci nu este nevoie să parcurgem în buclă restul din looseLeafTea. Pentru a-i spune lui JavaScript că
este suficient, adăugăm instrucțiunea break la bucla while. Făcând acest lucru, îi spune lui JavaScript
să treacă peste instrucțiunea while și să continue cu următoarea instrucțiune din script, care în cazul
nostru lipește dispoziția de alte câteva șiruri.
Astfel, bucla noastră while a eliminat corvoada de a trebui să scriem condiții if separate pentru
cele nouă elemente din looseLeafTea astfel:
var looseLeafTea = [
"Ghillidary",
"Kenilworth",
"Milima",
117
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"Keemun",
"Boisahabi",
"Manohari",
"Borpatra",
"Lukwah",
"Khongea"
];
var mood = "glum";
if (looseLeafTea[0] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[1] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[2] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[3] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[4] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[5] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[6] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[7] === "Borpatra") {
mood = "cheery";
}
if (looseLeafTea[8] === "Borpatra") {
mood = "cheery";
}
"Mă simt " + starea de spirit + " !";
// "Mă simt veselă!"
Pun pariu că vă bucurați acum că JavaScript oferă instrucțiuni de buclă, astfel încât să nu mai fie
nevoie să scrieți toate acele condiții if! Rețineți că, pe lângă faptul că economisește timpul
programatorilor, buclele permit JavaScript să lucreze inteligent. În bucla noastră while, JavaScript știa
că nu este necesar să interogheze ultimele trei elemente, deoarece a găsit deja "Borpatra".
din looseLeafTea prin intermediul metodei predefinite splice(), pe care o vom prezenta mai detaliat în
capitolul 5, care doar elimină elementul din matrice și apoi aduce în față elementele următoare
pentru a umple golul. În al doilea rând, vom introduce o instrucțiune continue pentru a întrerupe
iterația. Această instrucțiune va opri iterația curentă a buclei și va sări înapoi la începutul buclei
while cu o nouă iterație. Rețineți că acest lucru înseamnă că vom sări peste linia de cod i ++, astfel
încât contorul nu va fi incrementat. Este exact ceea ce dorim să se întâmple deoarece, atunci când am
eliminat elementul fals, JavaScript a adus toate elementele rămase înainte pentru a umple golul, astfel
încât există un nou element care ocupă acum poziția vechiului element fals. Din acest motiv, dorim să
facem o buclă de două ori peste același index din matrice pentru a ne asigura că acoperim toate
elementele. În cele din urmă, haideți să creștem i în cadrul unei clauze else doar pentru a face
lucrurile să se citească mai bine.
Deci, modificați exemplul anterior în felul următor și faceți clic pe Run în Firebug:
var looseLeafTea =
["Ghillidary",
"",
"Kenilworth",
"Milima",
,
"Keemun",
"Boisahabi",
"Manohari",
"Borpatra",
"Lukwah",
"Khongea"
];
var mood = "glum";
var i = 0;
while (i < looseLeafTea.length) {
if (looseLeafTea[i] === "Borpatra") {
mood = "cheery";
pauză;
} else if (! looseLeafTea[i]) {
looseLeafTea.splice(i, 1);
continue;
} else {
i ++;
}
}
"Mă simt " + starea de spirit + " !";
// "Mă simt veselă!"
Înainte de a trece mai departe, să verificăm dacă JavaScript a eliminat valorile "" și nedefinite din
looseLeafTea. Faceți clic pe Clear și apoi interogați looseLeafTea în felul următor, verificând munca dvs.
cu ajutorul figurii 4-9:
looseLeafTea;
// ["Ghillidary", " Kenilworth", " Milima", " Keemun", " Boisahabi", "Manohari",
// "Borpatra", " Lukwah", "Khongea"]
Deci, asta este. JavaScript a șters elementele "" și undefined, așa cum am dorit.
119
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Figura 4-9. Eliminarea elementelor care conțin "" sau undefined din looseLeafTea
120
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
} else if (! looseLeafTea[i]) {
looseLeafTea.splice(i, 1);
continue;
} else {
i ++;
}
}
returnează "glum";
}
"Mă simt " + findTea("Kenilworth") + " !".";
// "Mă simt veselă!"
După cum ilustrează figura 4-10, invocarea funcției noastre findTea() se evaluează la "vesel" sau
"posomorât"
în funcție de faptul că JavaScript poate găsi valoarea parametrului ceai în looseLeafTea.
Figura 4-10. În interiorul unei funcții, puteți întrerupe o buclă while cu o instrucțiune return.
Aici, === va compara "scorțișoară" cu "coajă de lămâie", indiferent de situație, deoarece JavaScript are
întotdeauna nevoie de cel puțin o rundă de buclă do while. Ulterior, JavaScript va face o altă iterație
numai dacă i < spices.length revine la true. Deoarece spices.length este evaluat la 15, JavaScript va
rula calea do while de 15 ori, cu excepția cazului în care îi spunem altfel cu o instrucțiune break, ceea
ce facem în cazul în care găsim "coajă de lămâie" în condimente. În cele din urmă, variabila de buclă i
conține indexul după care interogăm elementele din condimente, așa că îl creștem pe i cu operatorul
++ la sfârșitul traseului. În acest fel, JavaScript poate decide dacă mai are rost să ia o altă rută
ocolitoare.
În cazul în care JavaScript găsește "coajă de lămâie" în timp ce scotocește prin condimente, atribuie
false la putTheKaiboshOn. Această variabilă, la rândul său, permite JavaScript să decidă dacă rețeta
noastră este fezabilă. Dacă putTheKaiboshOn este false, JavaScript tipărește "Dă-i d r u m u l !". În caz
contrar, imprimă "Nu se poate face!". Testați dacă codul dvs. funcționează în ambele sensuri, rulând
exemplul cu și fără "coaja de lămâie" în matricea de condimente.
Înainte de a trece mai departe, haideți să refacem bucla noastră do while ca o buclă while. Acest lucru
ilustrează faptul că JavaScript preia necondiționat prima buclă de tip do while și apoi le preia
condiționat pe toate cele ulterioare. Prin urmare, pentru a emula un astfel de comportament cu o buclă
while, ar trebui să tastați de două ori calea de acces, astfel:
calea
while (expresie) path
Având în vedere acest lucru, faceți clic pe Clear în ambele panouri Firebug și să încercăm să facem acest
lucru:
var condimente
=
["scorțișoară"
, "ghimbir",
123
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"nucșoară",
"cuișoare",
"semințe de
susan", "piper",
"rozmarin",
"tarhon",
"busuioc",
"mac",
"semințe de
mac", "coajă
de lămâie",
"vanilie",
"oregano",
"piper",
"cimbru",
"cimbru"
];
var putTheKaiboshOn = true;
var i = 0;
if (spices[i] === "coajă de lămâie") {
putTheKaiboshOn = false;
} else {
i ++;
while (i < spices.length) {
if (spices[i] === "coajă de lămâie")
{ putTheKaiboshOn = false;
break;
}
i ++;
}
}
(putTheKaiboshOn) ? "Nu se poate face!" : "Dă-i d r u m u l !";
// "Dă-i d r u m u l !"
După cum ilustrează figura 4-12, echivalentul while al buclei noastre do while este mult mai verbos.
Prin urmare, pentru situațiile în care doriți ca JavaScript să parcurgă o cale de una sau mai multe ori,
este mult mai elegant să controlați fluxul cu do while decât cu while. Mulți începători se feresc de do
while. Nu fiți unul dintre ei!
124
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Figura 4-12. Înlocuirea unei bucle do while cu o buclă while necesită ceva efort!
var theFall = [
"Chasing Pirates",
"Even Though",
"Light as a Feather",
"Young Blood",
"N-aș avea nevoie de
tine", "Waiting",
"It's Gonna Be",
"You've Ruined Me",
"Back to Manhattan",
"Stuck",
"Decembrie",
"Tell Yer Mama",
"Omul orei"
];
var song = "Back to Manhattan";
for (var i = 0, j = 0; i < theFall.length; i ++) {
if (theFall[i] === song) {
j = i + 1;
pauză;
}
}
song + (j > 0 ? " este piesa " + j : " nu este") + " de pe The Fall.";
// "Back to Manhattan este piesa 9 de pe albumul The Fall."
expresiile de inițializare nu sunt reevaluate. Aceasta înseamnă că JavaScript le execută doar prima
dată. Rețineți, de asemenea, că folosim operatorul virgulă pentru a separa cele două expresii de
inițializare. Amintiți-vă din capitolul 3 că acest lucru face ca i = 0 și j = 0 să conteze ca o singură
expresie și nu două. Așadar, operatorul virgulă face pentru expresii ceea ce fac acoladele pentru
instrucțiuni - face ca două sau mai multe expresii să conteze ca una singură.
■ Sfat În cazul în care ați declarat anterior variabilele buclei, i și j în exemplul nostru, cuvântul cheie var este
opțional.
127
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"LunaRacer": 6.6,
"Air Max": 13,
"LunarGlide": 10.2,
"Zoom Streak XC": 7,
"Free": 8.6,
"Mayfly": 4,
"Zoom Vomero": 11.6,
"LunarElite": 9.7
}
var myOptions = [];
for (var shoe in shoes) { if
(shoes[shoe] >= 10) {
myOptions.push(shoe);
}
}
myOptions;
// ["Air Max", " LunarGlide", "Zoom Vomero"]
Bucla noastră for in a eliminat corvoada de a scrie următoarele opt condiții if:
if (shoes["LunaRacer"] >= 10) {
myOptions.push("LunaRacer");
}
if (shoes["Air Max"] >= 10) {
myOptions.push("Air Max");
}
if (shoes["LunarGlide"] >= 10) {
myOptions.push("LunarGlide");
}
if (shoes["Zoom Streak XC"] >= 10) {
myOptions.push("Zoom Streak XC");
}
128
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
129
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
pauză;
cazul 57:
name = "Zach Duke";
break;
cazul 48:
name = "Javier Lopez";
break;
cazul 28:
name = "Paul Maholm"; break;
cazul 34:
name = "Daniel McCutchen";
break;
cazul 47:
name = "Evan Meek";
break;
cazul 37:
name = "Charlie Morton";
break;
cazul 49:
name = "Ross Ohlendorf";
break;
cazul 62:
name = "Hayden Penn"; break;
cazul 43:
name = "Jack Taschner";
break;
cazul 41:
name = "Ryan Doumit";
break;
cazul 35:
name = "Jason Jaramillo";
break;
cazul 13:
name = "Ronny Cedeno";
break;
cazul 6:
name = "Jeff Clement";
break;
cazul 2:
name = "Bobby Crosby";
break;
cazul 3:
name = "Akinori Iwamura";
break;
cazul 15:
name = "Andy LaRoche";
break;
cazul 19:
name = "Ryan Church";
break;
cazul 46:
name = "Garrett Jones";
130
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
pauză;
cazul 22:
name = "Andrew McCutchen";
break;
cazul 85:
name = "Lastings Milledge";
break;
cazul 58:
name = "John Raynor"; break;
cazul 24:
name = "Delwyn Young";
break;
implicit:
name = "nu este purtat de niciun Pirat";
}
return jersey + " este " + nume + " .";
}
Au trecut doar două meciuri din acest sezon, dar uriașul care poartă 46 de ani a reușit deja trei home run-uri.
Una dintre ele a ajuns chiar în râul Allegheny, care trece pe lângă PNC Park, unde joacă Pirates. Așadar, să
trecem 46 la namePirate() și să aflăm cine este acel bătăuș. Verificați munca dvs. cu ajutorul figurii 4-15.
function namePirate(jersey) {
var name;
switch(jersey) {
cazul 77:
name = "D.J. Carrasco";
break;
cazul 53:
name = "Brendan Donnelly";
break;
cazul 29:
name = "Octavio Dotel";
break;
cazul 57:
name = "Zach Duke";
break;
cazul 48:
name = "Javier Lopez";
break;
cazul 28:
name = "Paul Maholm"; break;
cazul 34:
name = "Daniel McCutchen";
break;
cazul 47:
name = "Evan Meek";
break;
cazul 37:
name = "Charlie Morton";
break;
cazul 49:
131
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
132
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Figura 4-15. Maparea numerelor de tricouri cu nume pentru Pittsburgh Pirates cu ajutorul unui comutator
prima bază), trecând 3 la namePirate(). Amintiți-vă din capitolul 3 că [] își convertește operandul
într-un șir de caractere; prin urmare, puteți trece fie 3, fie "3" la namePirate(). Întrucât numerele
necesită mai puține apăsări de taste, să trecem 3. Apoi verificați-vă munca cu ajutorul figurii 4-16.
var pirates = {
"77": "D.J. Carrasco",
"53": "Brendan Donnelly",
"29": "Octavio Dotel",
"57": "Zach Duke",
"48": "Javier Lopez",
"28": "Paul Maholm",
"34": "Daniel McCutchen",
"47": "Evan Meek",
"37": "Charlie Morton",
"49": "Ross Ohlendorf",
"62": "Hayden Penn",
"43": "Jack Taschner",
"41": "Ryan Doumit",
"35": "Jason Jaramillo",
"13": "Ronny Cedeno",
"6": "Jeff Clement",
"2": "Bobby Crosby",
"3": "Akinori Iwamura",
"15": "Andy LaRoche",
"19": "Ryan Church",
"46": "Garrett Jones",
"22": "Andrew McCutchen",
"85": "Lastings Milledge",
"58": "John Raynor",
"24": "Delwyn Young"
};
function namePirate(jersey) {
return jersey + " este " + (pirates[jersey] ? pirates[jersey] : "nu este purtat de un Pirat") + " .";
}
namePirate(3);
// "3 este Akinori Iwamura."
135
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Dacă adoptați această abordare, puteți accelera codul de șapte ori. Da, e mult! Deci, curățați de două
ori Firebug și vă voi arăta cum.
În primul rând, să creăm un obiect prin care să facem o buclă. Hmm. Bine, m-am gândit la unul:
mai sunt câteva meciuri rămase din sezonul regulat al NHL și mai mulți jucători se luptă pentru trofeul
Rocket Richard, care este acordat anual celui mai bun marcator de goluri. În momentul de față, Sidney
Crosby, de la Pittsburgh Penguins, este cel mai bun marcator, cu 49 de goluri. Să creăm un obiect care
să conțină primii 20 de marcatori care intră în ultimul weekend de joc:
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
"Heatley": 39,
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
};
Acum, cu două meciuri înainte de final, cei mai mulți dintre acești jucători nu au nicio șansă de a-l
depăși pe Crosby și de a câștiga Rocket Richard. Așadar, nu numai că enumerarea fiecărui membru al
topTwenty cu o buclă for in ar fi lentă, dar ar fi și irațională. Pentru a nu părea ridicoli, haideți să
creăm o matrice numită RocketRichard care să conțină doar numele celor patru jucători care au șanse
să termine pe primul loc la goluri. Dacă tot suntem aici, să creăm un șir de note pentru mai târziu:
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
"Heatley": 39,
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
137
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"Jokinen": 30,
"Kane": 30
};
var rocketRichard = ["Ovechkin", " Crosby", "Marleau", "Stamkos"]], note = "";
Acum să ordonăm numele din rocketRichard după obiective și apoi după nume. Pentru a face acest
lucru, vom folosi metoda sort() pe care fiecare array, inclusiv rocketRichard, o definește pentru a putea
ordona elementele sale. Vom aborda sort() și alte metode Array în capitolul următor. Așa că,
deocamdată, tastați cu atenție!
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
"Heatley": 39,
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
};
var rocketRichard = ["Ovechkin", " Crosby", "Marleau", "Stamkos"]], note = "";
rocketRichard.sort(function(p1, p2) {
var d = topTwenty[p2] - topTwenty[p1]; if
(d !== 0) {
return d;
} else {
return (p1 < p2) ? -1 : 1;
}
});
Acum putem codifica fie o buclă for, while sau do while pentru a enumera indirect membrii din
topTwenty prin intermediul numelor membrilor din rocketRichard. Să folosim o buclă for. Aceasta va crea
un șir în note care va conține lista celor mai buni patru marcatori de goluri. După bucla for, să tăiem ",
" de la sfârșitul notei cu String.slice(), o metodă pe care am descris-o în Capitolul 2. Apoi faceți clic pe
Run (Executare) și verificați munca dvs. cu ajutorul figurii 4-17.
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
138
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"Heatley": 39,
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
};
var rocketRichard = ["Ovechkin", " Crosby", "Marleau", "Stamkos"], note = "";
rocketRichard.sort(function(p1, p2) {
var d = topTwenty[p2] - topTwenty[p1];
if (d== 0) {
return d;
} else {
return (p1 < p2) ? -1 : 1;
}
});
for (var i = 0; i < rocketRichard.length; i ++) {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]]] + ", ";
}
note.slice(0, -2);
// "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
Apropo, exemplul anterior ilustrează un avantaj ascuns în enumerarea membrilor obiectului cu
ajutorul unui tablou auxiliar. Acest lucru vă permite să stabiliți ordinea în care sunt enumerați
membrii.
139
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Acum că știți cum să înlocuiți un for din sloth cu un for, while sau do while gazelle, haideți să
explorăm câteva modalități de a le face pe cele trei mai rapide. Să începem cu bucla noastră for din
exemplul anterior.
În primul rând, JavaScript poate interoga variabilele locale din cadrul unei funcții sau variabilele
globale din afara unei funcții mai rapid decât membrii obiectului, cum ar fi lungimea - de două ori mai
rapid în Explorer 7 și 8. Așadar, în loc să interogăm de nenumărate ori lungimea în expresia booleană, i
< rocketRichard.length, să facem acest lucru o singură dată în expresia de inițializare, înlocuind i = 0
cu i = rocketRichard.length. În al doilea rând, este mai rapid să iterăm peste o matrice în sens invers,
deoarece acest lucru oferă o modalitate de a combina expresia booleană cu expresia de creștere sau
descreștere. Prin urmare, omiteți-o pe aceasta din urmă și descreșteți variabila de buclă i în expresia
booleană. La rândul său, deoarece acum iterăm peste tabloul în sens invers, trebuie să modificăm
literalul funcției pe care îl transmitem la sort(), astfel încât rocketRichard să fie ordonat de la cele mai
puține la cele mai multe goluri și apoi de la Z la A. Efectuați următoarele modificări, faceți clic pe Run
(Executare) și apoi verificați-vă munca cu ajutorul figurii 4-18:
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
"Heatley": 39,
140
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
}
var rocketRichard = ["Ovechkin", " Crosby", "Marleau", "Stamkos"], note = "";
rocketRichard.sort(function(p1, p2) {
var d = topTwenty[p1] - topTwenty[p2];
if (d !== 0) {
return d;
} else {
return (p2 < p1) ? -1 : 1;
}
});
for (var i = rocketRichard.length; i --; ) {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]]] + ", ";
}
note.slice(0, -2);
// "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
Rețineți că în i --, operatorul -- se află în poziția postdecreștere. De ce contează acest lucru?
Contează din mai multe motive. În primul rând, dacă ați fi scris -- i în loc de i --, JavaScript nu ar fi
interogat niciodată al patrulea element din rocketRichard. Pe de altă parte, dacă rocketRichard ar fi fost
gol, adică lungimea sa ar fi fost 0, atunci bucla noastră for nu s-ar fi oprit niciodată din iterare.
Așadar, asigurați-vă că -- se află în poziția post-decreștere!
141
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
În regulă, acum să încercăm să rescriem bucla noastră rapidă for ca o buclă while la fel de rapidă.
Trebuie doar să mutăm inițializarea lui i la rocketRichard.length într-o instrucțiune separată, înainte
de bucla while, și să descreștem i în expresia booleană. Efectuați aceste două modificări rapide,
astfel, și faceți clic pe Run:
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
"Heatley": 39,
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
142
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
}
var rocketRichard = ["Ovechkin", " Crosby", "Marleau", "Stamkos"], note = "";
rocketRichard.sort(function(p1, p2) {
var d = topTwenty[p1] - topTwenty[p2];
if (d== 0) {
return d;
} else {
return (p2 < p1) ? -1 : 1;
}
});
var i = rocketRichard.length;
while (i --) {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]]] + ", ";
}
note.slice(0, -2);
// "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
În cele din urmă, să rescriem bucla noastră snappy while sub forma unui snappy do while. Acest
lucru are sens, deoarece dorim să rulăm bucla cel puțin o dată. Bine, de patru ori. Ținând cont de acest
lucru, asigurați-vă că inițializați i la o valoare mai mică decât rocketRichard.length. În caz contrar,
nota va conține "undefined: undefined, Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43", deoarece
nu există niciun element cu un indice 4 în rocketRichard. Modificați bucla while loop din exemplul
anterior în felul următor, apoi faceți clic pe Run:
var topTwenty = {
"Crosby": 49,
"Ovechkin": 48,
"Stamkos": 48,
"Marleau": 43,
"Gaborik": 41,
"Kovalchuk": 40,
"Heatley": 39,
"Semin": 39,
"Parise": 37,
"Burrows": 35,
"Kopitar": 34,
"Ryan": 34,
"Carter": 33,
"Nash": 33,
"Iginla": 32,
"Penner": 32,
"Backstrom": 31,
"Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
}
var rocketRichard = ["Ovechkin", " Crosby", "Marleau", "Stamkos"], note = "";
rocketRichard.sort(function(p1, p2) {
143
CAPITOLUL 4 ■ CONTROLUL FLUXULUI
Rezumat
În acest capitol, ați învățat cum să controlați fluxul cu ajutorul instrucțiunilor condiționale if și
switch și cu instrucțiunile while, do while, for și for in looping. De asemenea, ați învățat să luați
decizii, așa cum face JavaScript, cu ajutorul expresiilor booleene. true este un semnal verde pentru a
face ceva, iar false este un semnal roșu pentru a nu face ceva.
În capitolul următor, vom aprofunda obiectele și array-urile, tipuri de date de tip folder pentru
organizarea datelor. Luați-vă o pauză binemeritată și ne vedem acolo!
144
CHAPTER 5
■■■
Moștenirea membrilor
În acest capitol, voi aborda o caracteristică a JavaScript numită moștenire, care este un instrument foarte
util și puternic. Ea ne permite să scriem cod mult mai ordonat și mai eficient, deoarece este o modalitate
excelentă de a organiza codul în mici bucățele utile. Voi acoperi următoarele patru moduri de abordare a
moștenirii, fiecare dintre ele având propriul loc în programele dumneavoastră:
• Clasică
• Prototip
• Copie adâncă
• Mixin
După cum probabil ați ghicit din titlul capitolului, acest capitol se referă numai la moștenirea
membrilor; capitolul 6 se referă la modul de utilizare a moștenirii cu funcții de obiect.
Pentru a înțelege mai bine moștenirea, trebuie mai întâi să cunoașteți puțin mai bine obiectele,
așa că voi începe prin a aborda constructorii de obiecte pentru a vedea cum sunt definite și create
obiectele. Ați văzut deja câțiva constructori în capitolele anterioare (orice lucru precedat de cuvântul
cheie new este un constructor), dar în acest capitol veți vedea cum să definiți constructori pentru
dumneavoastră.
Afinele sunt fructele mele preferate - mama mea m-a poreclit Blueberry când eram copil din acest
motiv și pentru că rimează cu Terry - așa că am fost foarte afectat de moartea lui Wild Maine Blueberry.
S-ar putea chiar să nu mai vorbesc pentru o vreme.
Dar timpul vindecă toate rănile. Sau, în acest caz, o mașină de făcut înghețată a făcut-o. Așa că,
acum îmi bat propria mea înghețată de afine Wild Maine Blueberry după următoarea rețetă. Rețineți
că din cele două cești de afine sălbatice din Maine, am făcut piure una din cele două cești de afine
sălbatice din Maine, a căror pulpă este îndepărtată împreună cu păstaia de vanilie și semințele,
strecurând crema în stil francez printr-o sită cu ochiuri fine.
145
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
• 6 gălbenușuri de ou
146
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-1. Metoda console.dir() din Firebug tipărește membrii proprii și moșteniți ai unui obiect.
Acum, de data aceasta, să batem un sfert de litru mai puțin preferabil, dar totuși delicios, cu afine
sălbatice congelate Dole și o fasole tahitiană, verificând munca noastră cu ajutorul figurii 5-2:
var WildMaineBlueberry = function(afine, vanilie) {
this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
147
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
};
WildMaineBlueberry.prototype = {
heavyCream: [1, "cup", "Organic Valley"],
halfHalf: [1, " c e a ș c ă ", "Organic Valley"],
zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
freshLemonJuice: [2, "tsp"]
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");
consola.dir(wildMaineBlueberry);
Figura 5-2. Bătând un sfert de litru cu afine sălbatice congelate Dole și o fasole Tahitiană
În cele din urmă, haideți să fim ding-dongs și să uităm să invocăm WildMaineBlueberry() cu new.
După cum arată figura 5-3, WildMaineBlueberry() returnează undefined, iar acum există variabile
globale numite blueberries și vanilla. Grozav googly moogly!
var WildMaineBlueberry = function(afine, vanilie) {
this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
heavyCream: [1, "cup", "Organic Valley"],
halfHalf: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
freshLemonJuice: [2, "tsp"]
};
var wildMaineBlueberry = WildMaineBlueberry();
tip de afine sălbaticeMaineBlueberry;
// "nedefinit"
afine;
// [2, "ceașcă", "afine proaspete sălbatice din
Maine"] vanilie;
// [1, "bean", "Madagascar Bourbon"]
148
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-3. Nu uitați să apelați constructori precum WildMaineBlueberry() cu new, altfel veți crea sau
suprascrie variabile globale.
Acum că știți cum să creați obiecte personalizate folosind un constructor, este timpul să vorbim
despre moștenire. Aceasta vă va arăta cum să profitați de constructori.
Moștenirea clasică
La fel ca Wild Maine Blueberry, multe arome de înghețată încep cu o bază de vanilie, la care se adaugă
apoi alte ingrediente. Acest lucru este ceva ce ne-ar plăcea să facem adesea în JavaScript; cu alte
cuvinte, dacă avem un obiect care are anumiți membri dezirabili, putem lua acel obiect și să-i adăugăm
alți membri pentru a crea un alt tip de obiect. Noul obiect se bazează pe cel vechi și are toți membrii
celui vechi, în plus față de toți cei noi pe care i-am adăugat. Acest lucru înseamnă că putem plasa
membri comuni utili într-un obiect și apoi să ne bazăm pe acesta pentru alte obiecte mai specializate,
precum și să creăm versiuni mai specializate ale obiectelor utile în codul nostru. Funcția din JavaScript
care permite acest lucru se numește moștenire, deoarece noul obiect moștenește membrii vechiului
obiect.
Înapoi la înghețată pentru a vedea moștenirea în acțiune: cea mai bună înghețată de vanilie se
face prin înmuierea boabelor de vanilie, mai degrabă decât prin adăugarea de extract de vanilie. Există
trei tipuri de boabe de vanilie. Madagascar Bourbon este cea mai comună. Eu le prefer pe acestea în
locul boabelor mai florale din Tahiti sau a celor mexicane mai intense.
Așadar, să creăm un constructor VanillaBean() care acceptă opțional un parametru vanilla, care va
conține tipul de fasole sub forma unui șir de caractere. Prin intermediul operatorului ?:, vom alege în
mod implicit vanilia "Madagascar Bourbon":
var VanillaBean = function(vanilla) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
}
Adesea, pun la macerat un baton de scorțișoară Saigon cu boabele de vanilie. Acest lucru funcționează foarte
bine pentru
arome de fructe, cum ar fi piersica sau dacă serviți înghețata cu plăcintă. Deci, să adăugăm un parametru
boolean opțional numit scorțișoară. Prin intermediul operatorului &&&, VanillaBean() va adăuga un baton
de scorțișoară numai dacă scorțișoară este adevărat. Rețineți că, în măsura în care && are o precedență
mai mare decât =, trebuie să punem expresia = între paranteze.
var VanillaBean = function(vanilie, scorțișoară) {
149
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
gălbenușuri: [6]
};
După ce am făcut acest lucru, să creăm o instanță VanillaBean() numită vanilla, verificând munca
noastră cu ajutorul figurii 5-4. Rețineți că vanilla are proprii membri vanilla și cinnamon, dar
moștenește alți membri din VanillaBean.prototype.
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var vanilla = new VanillaBean("Tahitian", true);
console.dir(vanilla);
150
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-4. vanilla are proprii membri vanilla și cinnamon, dar moștenește alți membri de la
VanillaBean.prototype.
Acum să creăm un constructor Coffee() care va moșteni din VanillaBean(). Cu excepția cazului în
care se specifică altfel într-un parametru opțional al cafelei, vom prepara Starbucks Espresso măcinat
grosier cu o boabă de vanilie Bourbon din Madagascar.
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var Coffee = function(coffee) {
this.coffee = coffee || [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
Rețineți că Coffee.prototype conține un singur membru, vanilla, care conține [1, "bean",
"Madagascar Bourbon"]], deoarece nu am furnizat parametrul opțional scorțișoară la constructorul
Vanilla(). Astfel, pentru Coffee(), lanțul prototipurilor ar fi următorul:
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
Coffee.prototype = {
vanilie: [1, "bean", "Madagascar Bourbon"],
};
151
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
După ce am făcut acest lucru, să creăm o instanță Coffee() numită coffee, verificând munca
noastră cu ajutorul figurii 5-5. Observați că coffee are propriul membru coffee, dar moștenește alți
membri din Coffee.prototype și VanillaBean.prototype.
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var Coffee = function(coffee) {
this.coffee = coffee || [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean(); var
coffee = new Coffee();
console.dir(coffee);
Figura 5-5. coffee are propriul membru coffee, dar moștenește alți membri din Coffee.prototype și
VanillaBean.prototype.
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var Coffee = function(coffee) {
this.coffee = coffee || [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
După ce am făcut acest lucru, să creăm o instanță Chocolate() numită chocolate, verificând munca
noastră cu ajutorul figurii 5-6. Rețineți că ciocolata are proprii membri cacao și dulce-amărui și
moștenește alți membri din Chocolate.prototype și VanillaBean.prototype.
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var Coffee = function(coffee) {
this.coffee = coffee || [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
Figura 5-6. Ciocolata are proprii membri cacao și dulce-amăruie și moștenește alți membri de la
Chocolate.prototype și VanillaBean.prototype.
Deși nu se află în topul meu 10, Ben & Jerry's Mint Chocolate Chunk este destul de bună, așa că
haideți să definim un constructor MintChocolateChunk() și să înlănțuim MintChocolateChunk.prototype
la Chocolate.prototype prin invocarea Chocolate() cu operatorul new. Rețineți că frunzele de mentă vor
fi puse la macerat împreună cu păstaia și semințele de vanilie, iar mai târziu le veți îndepărta pe
amândouă prin strecurare printr-o sită cu ochiuri fine. Rețineți, de asemenea, că veți adăuga bucățile de
ciocolată dulce-amăruie Callebaut chiar la sfârșitul fazei de batere.
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var Coffee = function(coffee) {
this.coffee = coffee || [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cacao, dulce-amăruie) {
this.cacao = cacao || [3/16, "cup", "Callebaut"];
this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.gălbenușuri = [4];
154
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-7. mintChocolateChunk are propriul membru mint și moștenește alți membri de la
MintChocolateChunk.prototype, Chocolate.prototype și VanillaBean.prototype.
156
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-8. mintChocolateChunk este o instanță a patru tipuri, ceea ce înseamnă că moștenește membri de
la patru constructori.
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cacao, dulce-amăruie) {
this.cacao = cacao || [3/16, "cup", "Callebaut"];
this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.gălbenușuri = [4];
var MintChocolateChunk = function(mint) {
this.mint = mint || [1, "ceașcă", "frunze proaspete de mentă"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, " bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
ștergeți MintChocolateChunk.prototype.cocoa;
var vanilla = new VanillaBean("Tahitian", true);
var coffee = new Coffee();
var chocolate = new Chocolate([1/4, "cup", "Bensdorp"]);
var mintChocolateChunk = new MintChocolateChunk();
159
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-9. Orice instanță de VanillaBean, Coffee, Chocolate sau MintChocolateChunk are în comun cu heavyCream
array, [1, "cup", "Organic Valley"].
Care sunt implicațiile partajării membrilor moșteniți? Modificarea unui membru moștenit
modifică imediat toate instanțele, atât cele vechi, cât și cele noi, care îl moștenesc. Dacă vă gândiți că
acest lucru ar face mai ușor să vă îngrijiți codul atunci când ceva trebuie să se schimbe, vă datorez o
prăjitură Smiley!
MintChocolateChunk.prototype.bittersweet[2] = "Lindt";
console.dir(mintChocolateChunk);
Figura 5-11. Adăugarea a patru batoane Heath la VanillaBean.prototype are efecte neintenționate, și
anume că celelalte trei obiecte includ și ele batoane Heath.
Figura 5-12. Nu există nicio legătură în lanț de prototipuri între Strawberry.prototype și Blueberry.prototype.
};
Blueberry.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [3],
vanilie: [1, "bean", "Madagascar Bourbon"]
};
var Strawberry = function(strawberry) {
this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"]];
};
Strawberry.prototype = new Blueberry(); delete
Strawberry.prototype.blueberry; delete
Strawberry.prototype.freshLemonJuice; var
blueberry = new Blueberry();
var strawberry = new Strawberry();
console.dir(blueberry);
console.dir(strawberry);
Aceasta este vestea bună. Acum, cea rea. Deoarece Blueberry.prototype și Strawberry.prototype
sunt același obiect, nu există nicio modalitate de a adăuga, șterge sau modifica membrii moșteniți
pentru Strawberry() fără a schimba în mod identic brazii moșteniți pentru Blueberry() și viceversa.
Pentru a ilustra acest aspect, încercați următorul exemplu, verificând munca dvs. cu ajutorul figurii 5-
14:
var Berry = function() {}
Berry.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
165
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
gălbenușuri: [3],
vanilie: [1, "bean", "Madagascar Bourbon"]
};
Figura 5-14. Adăugarea unui membru cinnamon la Blueberry.prototype nu diferă cu nimic de adăugarea lui la
Strawberry.prototype sau Berry.prototype, deoarece sunt același obiect.
this.cherries = [2, "ceașcă, fără sâmburi și tăiate în două", cherry ? cherry : "Bing"];
this.bittersweet = [1, "ceașcă, tocat grosier", bittersweet ? bittersweet : "Callebaut"];
};
extend(CherryGarcia, Strawberry);
CherryGarcia.prototype.sugar = [9/16, "cup"];
var strawberry = new Strawberry();
var cherryGarcia = new CherryGarcia();
console.dir(strawberry);
console.dir(cherryGarcia);
170
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-16. CherryGarcia() este o clonă a lui Cherry() care adaugă bucăți de dulceață amară Callebaut.
Moștenirea prototipală
Deseori, veți dori să creați un obiect care este destul de asemănător cu un altul. Tehnicile pe care le-am
văzut anterior pot face acest lucru, dar a fost nevoie de o cantitate mare de muncă pentru a scrie toți
constructorii și pentru a înlănțui prototipurile lor dacă există multe asemănări între obiecte. Pentru
astfel de circumstanțe, vom renunța la moștenirea clasică anterioară și vom apela în schimb la
moștenirea prototipală, care clonează din prototipuri, mai degrabă decât să folosim moștenirea precum
cea pe care am văzut-o anterior. Deși ECMAScript 5 definește o funcție Object.create() pentru
moștenirea prototipală, niciun browser nu o implementează încă. Așadar, vom scrie propria noastră
funcție de moștenire prototipală numită clone() în timp ce așteptăm ca Firefox, Safari, Opera și, în cele
din urmă, Internet Explorer să implementeze Object.create().
■ Notă Versiunile beta ale următoarelor versiuni majore pentru Internet Explorer, Firefox, Chrome și Safari sunt compatibile
cu
Object.create(), așa că s-ar putea să nu mai dureze mult până când va fi utilizat în general.
clone() funcționează cu un parametru numit donor, care conține obiectul pe care doriți să îl clonați.
La fel ca în cazul extend(), vom crea o funcție constructor goală numită Proxy(). Spre deosebire de
extend(), vom seta Proxy.prototype la donor și nu la donor.prototype și vom returna un obiect gol,
care va moșteni atât membrii proprii, cât și pe cei moșteniți de la donor. Vom adăuga apoi orice
membri suplimentari de care avem nevoie la obiectul gol.
var clone = function (donor) {
var Proxy = function () {};
Proxy.prototype = donor;
return new Proxy();
};
171
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Să zicem că avem un litru de înghețată de banane și vrem să facem un litru de Ben & Jerry's
Chunky Monkey pe baza conținutului înghețatei de banane. Am face acest lucru cu ajutorul funcției
clone(), așa cum se arată în exemplul următor și în figura 5-17:
var clone = function (donor) {
var Proxy = function () {};
Proxy.prototype = donor;
return new Proxy();
};
var banana = {
heavyCream: [1, " cup", "Organic Valley"],
Figura 5-17. Moștenirea prototipală este mult mai simplă decât cea clasică.
172
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Acum, Object.create() va primi un al doilea parametru opțional atunci când va sosi în cele din
urmă, care este un obiect care conține membrii care trebuie adăugați la clonă. Așadar, să definim o a
doua funcție numită emulate() care face același lucru în timp ce așteptăm zile mai bune:
var emulate = function (donor, more) { var
Proxy = function () {}, child, m;
Proxy.prototype = donor;
child = new Proxy(); for
(var m in more) {
child[m] = more[m];
}
întoarce copilul;
};
Acum, să spunem că am făcut un litru de înghețată de ciocolată și vrem să facem un litru de Ben &
Jerry's New York Super Fudge Chunk. Putem face acest lucru trecând sfertul de ciocolată plus
ingredientele suplimentare la emulate(). Încercați acest lucru, verificând munca dvs. cu ajutorul figurii 5-
18:
var emulate = function (donor, more) { var
Proxy = function () {}, child, m;
Proxy.prototype = donor;
child = new Proxy(); for
(var m in more) {
child[m] = more[m];
}
întoarce copilul;
};
var chocolate = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"],
Clonarea membrilor
Un alt mod de a clona un obiect este de a face o copie profundă a membrilor săi. Spre deosebire de
emulate() prezentat anterior, care face o copie superficială a membrilor (adică membrii tipului de
obiect sunt copiați prin referință), o copie profundă îi clonează recursiv pe aceștia. Să scriem o funcție
ajutătoare numită cloneMembers() care va clona un obiect prin copierea profundă a membrilor săi,
amânând explicația recursivității până la capitolul 6.
var cloneMembers = function (donor, d o n e e ) {
donee = donee || {};
for (var m in donor) {
if (typeof d o n o r [ m] === "object" && donor[m] !== null) {
donee[m] = typeof donor[m].pop === "function" ? [] : {};
cloneMembers(donor[m], donee[m]);
} else {
donatar[m] = donator[m];
}
}
retur donatar;
};
Acum, dacă vrem să facem un litru de Ben & Jerry's Coffee Heath Bar Crunch de la Vanilla Heath
Bar Crunch, îl vom clona pe acesta din urmă prin copierea profundă a membrilor săi. Apoi adăugăm un
membru Coffee, verificând munca noastră cu ajutorul figurii 5-19. Rețineți că coffeeHeathBarCrunch nu
moștenește membrii de la vanillaHeathBarCrunch prin intermediul lanțului de prototipuri. Mai degrabă,
coffeeHeathBarCrunch are copii profunde ale membrilor vanillaHeathBarCrunch. Așadar, de data aceasta
nu mai există un lanț de prototipuri.
174
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
175
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Mixins
În cele din urmă, putem crea un obiect prin copierea profundă a membrilor a două sau mai multe
obiecte. Această operațiune se numește mixin. Așadar, vom scrie o funcție merge() care primește o
matrice de mixini numită mixins și, opțional, un donat căruia să îi cloneze membrii. Rețineți că funcția
merge() va cere ca cloneMembers() să efectueze copia profundă.
var cloneMembers = function (donor, donee) {
donee = donee || {};
for (var m in donor) {
if (typeof d o n o r[m] === "object" && donor[m] !== null) {
donee[m] = typeof donor[m].pop === "function" ? [] : {};
cloneMembers(donor[m], donee[m]);
} else {
donatar[m] = donator[m];
}
}
retur donatar;
};
var french = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var philly = {
heavyCream: [2, " cup", "Organic Valley"],
178
k
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR
Figura 5-20. Crearea înghețatei italiene, Philadelphia și franțuzești cu ajutorul unei implementări mixin
Rezumat
În acest capitol, am explorat moștenirea membrilor prin intermediul implementărilor clasice,
prototipale, de copiere profundă și mixin. Cu toate acestea, de cele mai multe ori, metodele, adică
funcțiile, sunt cele care sunt moștenite. Vom aborda acest aspect și alte aspecte esențiale ale
funcțiilor în capitolul 6.
Luați o cupă binemeritată sau două din înghețata voastră preferată și ne vedem acolo.
179
CHAPTER 6
■■■
Funcții și array-uri
În capitolul anterior, ați învățat despre moștenire și ați văzut cum să transmiteți membri către obiectele
copil folosind moștenirea clasică, prototipală, copie profundă și mixin. După cum am menționat la
sfârșitul acelui capitol, de fapt, funcțiile sunt cele care sunt mai des transmise către obiectele copil.
Acest lucru se datorează faptului că procesele comune, furnizate de o funcție, sunt frecvent mai utile
decât datele comune, furnizate de membri. În acest capitol, voi aborda motivele pentru care ați dori să
utilizați funcții și cum să profitați de moștenirea funcțiilor. Există o mulțime de trucuri interesante de
învățat în acest domeniu și vom profita de multe dintre ele în capitolele ulterioare, după cum veți
vedea din abundentele referințe înainte. Cu alte cuvinte, acesta este un capitol destul de important.
Pe lângă subtipul de funcție, array-urile sunt un al doilea subtip al tipului de valoare obiect.
Array-urile sunt speciale în primul rând datorită metodelor predefinite pe care le moștenesc din
Array.prototype. Vom explora aceste metode în acest capitol.
De ce să folosiți funcții?
Ben & Jerry's, Häagen-Dazs și alte înghețate în stil franțuzesc sunt făcute prin crearea unei creme
satinate din smântână, lapte și gălbenușuri de ou. În comparație cu înghețata în stil Philadelphia, care
se face prin simpla batere a smântânii și a laptelui, înghețata în stil francez poate fi dificil de preparat
pentru un începător. Așadar, cel mai bine este să începi cu vanilie înainte de a te da în vânt. Err, aurirea
orhideei - aroma de vanilie derivă din fructul orhideei de vanilie.
În orice caz, cea mai delicioasă înghețată de vanilie se prepară prin infuzarea păstăii și a
semințelor de la o păstaie de vanilie, mai degrabă decât prin amestecarea extractului de vanilie, care
este mai puțin aromat. Boabele de vanilie diferă ca gust în funcție de locul în care sunt cultivate
orhideele de vanilie. Cele din Madagascar Bourbon sunt preferatele mele. În comparație cu acestea,
cele din Tahiti au o aromă mai blândă, iar cele mexicane sunt mai îndrăznețe.
Dacă sunteți un începător în materie de înghețată, luați-o ușor cu următoarea rețetă de vanilie
franceză înainte de a orna orhideea cu ciocolată, fructe și așa mai departe:
1 ceașcă de smântână pentru frișcă Organic Valley
2 căni, organic Valley half & half 5/8
cană, zahăr
6 gălbenușuri de ou
1 păstaie de vanilie Bourbon din Madagascar
181
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
• Presărând cu lingura de lemn, se strecoară crema printr-o sită cu ochiuri fine într-
un castron pentru a îndepărta păstaia de vanilie, semințele și eventualele
cocoloașe de cremă.
• Scoateți crema din baia de gheață și răciți-o în frigider cel puțin câteva ore, dacă
nu peste noapte, deoarece crema bine răcită îngheață mai bine.
Odată ce ați ajuns să stăpâniți vanilia franceză, este ușor să o înfrumusețați, deoarece multe arome
derivă din ea. De exemplu, pentru a face înghețată de cafea, nu trebuie decât să puneți la macerat 1/4
cană de boabe de espresso măcinate grosier cu păstaia și semințele de vanilie. Apoi, strecurați cu o
sită cu ochiuri fine măcinatele de espresso împreună cu păstaia și semințele de vanilie și eventualele
cocoloașe de cremă. Sau, pentru a face ciocolată, bateți 3/8 cană de cacao procesată olandeză -
recomand Callebaut - în gălbenușuri și smântână înainte de temperare.
Rețetele de înghețată în stil francez pentru vanilie, cafea și ciocolată prezentate aici sunt similare:
1 ceașcă de smântână pentru frișcă Organic Valley
2 căni, organic Valley half & half 5/8
cană, zahăr
6 gălbenușuri de ou
1 păstaie de vanilie Bourbon din Madagascar
182
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
doriți să creați o funcție pentru aceste declarații. Apoi, definiți parametrii pentru diferențe.
Constructorul și funcțiile ajutătoare din capitolul 5, cum ar fi CherryGarcia() și extend(), sunt exemple
bune în acest sens.
Știți deja când trebuie să creați o funcție. Prin urmare, în acest capitol, vom explora două
caracteristici vitale ale funcțiilor JavaScript care le fac deosebite. În primul rând, funcțiile sunt valori
care pot fi exprimate cu notație literală. În termeni tocilari, acest lucru înseamnă că JavaScript are funcții
de primă clasă. În al doilea rând, JavaScript are domeniul de aplicare al funcțiilor, ceea ce face ca
funcțiile să fie vitale pentru căutarea de variabile.
Deschideți firebug.html în Firefox, apoi apăsați F12 pentru a activa Firebug - dacă abia vă alăturați
nouă, întoarceți-vă la prefață pentru detalii despre cum să faceți acest lucru - și să începem să
explorăm funcțiile ca valori.
■ Notă Dacă salvați o funcție într-un obiect, funcția este denumită metodă.
183
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Care este diferența? În primul rând, declarațiile de funcții nu pot fi atribuite variabilelor,
membrilor sau elementelor. Altfel spus, trebuie să declarați funcția și apoi să-i atribuiți numele unei
variabile, unui membru sau unui element. Sunt doi pași în loc de unul. În plus, nu puteți trece o
declarație de funcție către o funcție. Mai degrabă, trebuie să transmiteți numele acesteia. Din nou, sunt
doi pași în loc de unul. În cele din urmă, declarațiile creează funcții care pot fi apelate înainte de a fi
definite, deși acest lucru nu este bine văzut. Din aceste motive și pentru a vă obișnui să folosiți
funcțiile ca valori, la fel ca obiectele sau booleenii, vom continua să folosim expresii de funcții mai
degrabă decât declarații pentru restul călătoriei noastre.
Membrii funcției
Funcțiile sunt valori de tip valoare de obiect, deci moștenesc membrii din Object.prototype, cum ar fi
valueOf(), precum și următorii membri din Function.prototype. Rețineți că
Function.prototype.constructor și Function.prototype.toString() suprascriu
Object.prototype.constructor și Object.prototype.toString().
constructor
length
apply()
bind()
call()
toString()
Function.prototype.constructor se referă doar la Function(), length conține numărul de parametri
numiți definiți pentru funcție, iar toString() conține definiția funcției sub forma unui șir de caractere.
Interogați-le pentru WildMaineBlueberry(). Rețineți că vom explora aplicațiile apply(), bind() și call()
imediat după aceea.
function WildMaineBlueberry (afine, vanilie) {
this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
}
WildMaineBlueberry.prototype = {
heavyCream: [1, "cup", "Organic Valley"],
halfHalf: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
freshLemonJuice: [2, "tsp"]
};
WildMaineBlueberry.constructor;
// Funcție()
WildMaineBlueberry.length;
// 2
WildMaineBlueberry.toString()
// "function WildMaineBlueberry(afine, v a n i l i e ) { this.blueberries = [2, "cup",
blueberries ? blueberries : "fresh wild Maine blueberries"]; this.vanilla = [1, "bean", vanilla ?
vanilla : "Madagascar Bourbon"]; }"
184
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
■ Sfat Este important de menționat că JavaScript inițializează un element în obiectul arguments pentru fiecare
parametru numit sau nenumit. Puteți utiliza acest obiect pentru a obține numărul de parametri utilizați
(arguments.length) sau pentru a obține numele funcției care a apelat funcția curentă (arguments.callee),
aspect pe care îl vom aborda puțin mai târziu, în secțiunea "Recursiune". De asemenea, puteți obține
parametri utilizând locul lor în lista de parametri (de exemplu, arguments[0] pentru a obține primul
parametru).
185
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
ECMAScript5. Să începem prin a schița o condiție if pentru membrii lipsă. Vă amintiți care este
valoarea unui membru lipsă?
Da, nedefinit.
Până în prezent, avem următoarele:
if (Object.defineProperty === undefined) {
}
if (Object.defineProperties === undefined) {
}
if (Object.create === undefined) {
}
Acum, în cadrul blocurilor if goale, creați membrii lipsă și atribuiți un literal de funcție gol.
Aveți grijă să nu omiteți punctul și virgula de după fiecare declarație de atribuire:
if (Object.defineProperty === undefined) {
Object.defineProperty = function () {
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function () {
};
}
if (Object.create === undefined) {
Object.create = function () {
};
}
Scrierea Object.defineProperty()
Cu Object.defineProperty(), puteți atribui o valoare unui membru, precum și să definiți dacă un
membru este inscriptibil, enumerabil sau ștergibil.
■ Notă Enumerable înseamnă că un membru este enumerat într-o buclă for in, în timp ce writable înseamnă
pur și simplu că putem atribui o valoare membrului. Prin urmare, puteți vedea că o matrice este enumerabilă,
la fel ca și proprietățile unui obiect.
• În al doilea rând, numele unui membru, sub forma unui șir de caractere, pe care doriți să îl
adăugați sau să îl modificați.
186
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
■ Rețineți că nu voi aborda descriptorii de accesori în această carte. Îi utilizați pentru a furniza o funcție
care este apelată ori de câte ori este accesată sau setată valoarea unei proprietăți.
187
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Scrierea Object.defineProperties()
Object.defineProperties() este un fel de versiune la plural a Object.defineProperty(). Adică, poate
crea sau modifica mai mult de un membru. Spre deosebire de Object.defineProperty(), acesta acceptă
doi parametri.
• Cel de-al doilea este un obiect care conține unul sau mai multe obiecte descriptor.
Deci, pentru browserele pre-ECMAScript 5, vom parcurge în buclă parametrul descriptorilor cu un for in
buclă, fără a lua în considerare membrii descriptorului, alții decât valoarea:
Scriere Object.create()
În cele din urmă, Object.create() funcționează cu doi parametri (amintiți-vă că am discutat despre create() în
capitolul 5).
• Cel de-al doilea, opțional, este un obiect care conține descriptori ai propriilor
membri care trebuie adăugați la obiectul copil.
Pentru browserele pre-ECMAScript 5, vom scrie o funcție similară cu clone() în capitolul 5. În cazul
în care parametrul opțional descriptors este definit, îl vom trece la Object.defineProperties():
if (Object.defineProperty === undefined) {
Object.defineProperty = function (obj, name, descriptor) {
obj[name] = descriptor.value;
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
188
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
obj[descriptor] = descriptors[descriptor].value;
}
}
};
}
if (Object.create === undefined) {
Object.create = function (parent, descriptors) {
var Proxy = function () {},
copil;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
}
console.log(Object.defineProperty.toString());
console.log(Object.defineProperties.toString());
console.log(Object.create.toString());
Acum, odată ce am scris aceste încărcătoare anticipate condiționate, putem reface extend() din
capitolul 5, astfel încât membrii constructorului pe care îi adăugăm la obiectele prototip copil și părinte
să nu fie enumerați într-o buclă for in. Altfel spus, membrii constructorului nostru se vor comporta ca
și cei nativi care sunt suprascriși în timpul înlănțuirii prototipurilor sau al înlocuirii prototipurilor. În
plus, vom seta writable la true și configurable la false, astfel încât membrul constructorului să poată fi
modificat, dar nu șters. În cele din urmă, vom face ca membrul din superclasa constructorului copil să
fie inscriptibil și configurabil, dar nu enumerabil. În acest fel, păstrăm opțiunea de a nu lăsa copilul să
moștenească de la un părinte.
if (Object.defineProperty === undefined) {
Object.defineProperty = function (obj, name, descriptor) {
obj[name] = descriptor.value;
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
}
}
190
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
};
}
if (Object.create === undefined) {
Object.create = function (parent, descriptors) { var
Proxy = function () {},
copil;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
var extend = function (child, p a r e n t , descriptors) {
child.prototype = Object.create(parent.prototype, descriptors);
Object.defineProperty(child.prototype, "constructor", {
value: child,
writable: true,
enumerable: false,
configurable: false
});
if (! parent.prototype.hasOwnProperty("constructor")) { Object.defineProperty(parent.prototype,
" constructor", {
valoare: părinte,
inscriptibil:
adevărat,
enumerabil: fals,
configurabil: fals
});
}
Object.defineProperty(child, "superclass", {
value: parent.prototype,
inscriptibil: adevărat,
enumerabil: fals,
configurabil: adevărat
});
};
În măsura în care writable, enumerable și configurable au valoarea implicită false, putem scrie mai
succint
extend() astfel. Asigurați-vă că ați eliminat virgula care urmează membrului descriptor final. La urma
urmei, descriptorii sunt doar literali de obiect, deci trebuie să respecte notația literală de obiect.
if (Object.defineProperty === undefined) {
Object.defineProperty = function (obj, name, descriptor) {
obj[name] = descriptor.value;
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
}
191
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
}
};
}
if (Object.create === undefined) {
Object.create = function (parent, descriptors) { var
Proxy = function () {},
copil;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
var extend = function (child, p a r e n t , descriptors) {
child.prototype = Object.create(parent.prototype, descriptors);
Object.defineProperty(child.prototype, "constructor", {
valoare:
child,
inscriptibil:
true
});
if (! parent.prototype.hasOwnProperty("constructor")) {
Object.defineProperty(parent.prototype, "constructor", {
valoare: parent,
scriere: true
});
}
Object.defineProperty(child, "superclass", {
valoare:
parent.prototype,
inscriptibil: true,
configurabil: true
});
};
Acum să refacem exemplul extend() din capitolul 5, în care am pus CherryGarcia() să moștenească din
Căpșuni. În browserele compatibile cu ECMAScript 5, cum ar fi Explorer 9 și Firefox 4,
Strawberry.prototype.constructor și CherryGarcia.prototype.constructor nu vor fi enumerate într-o
buclă for in sau șterse de operatorul delete. Mai mult, CherryGarcia.superclass nu va fi enumerat într-
o buclă for in, dar va fi șters de operatorul de ștergere. Pe de altă parte, în browserele pre-
ECMAScript 5, extend() ar crea membrii constructorului și ai superclasei prin simpla atribuire cu
ajutorul operatorului =. Astfel, metoda console.dir() a Firebug, care tipărește membrii enumerabili ai
unui obiect, ar tipări membrul constructor pentru căpșuni și cireșeGarcia dacă executați Firefox 3, ca
în figura 6-2, dar nu și dacă executați Firefox 4.
if (Object.defineProperty === undefined) {
Object.defineProperty = function (obj, name, descriptor) {
obj[name] = descriptor.value;
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
192
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
}
}
};
}
if (Object.create === undefined) {
Object.create = function (parent, descriptors) { var
Proxy = function () {},
copil;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
var extend = function (child, p a r e n t , descriptors) {
child.prototype = Object.create(parent.prototype, descriptors);
Object.defineProperty(child.prototype, "constructor", {
valoare: child,
inscriptibil:
true
});
if (! parent.prototype.hasOwnProperty("constructor")) {
Object.defineProperty(parent.prototype, "constructor", {
valoare: parent,
scriere: true
});
}
Object.defineProperty(child, "superclass", {
value: parent.prototype,
inscriptibil: adevărat,
configurabil: adevărat
});
};
inscriptibil: t r u e ,
enumerabil: true,
configurabil: true
}});
var strawberry = new Strawberry();
var cherryGarcia = new CherryGarcia();
console.dir(strawberry);
console.dir(cherryGarcia);
Figura 6-2. strawberry și cherryGarcia vor avea membri enumerabili ai constructorului în browserele
pre-ECMAScript 5.
194
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Încărcare leneșă
O altă implicație a faptului că funcțiile sunt valori este că puteți modifica condiționat valoarea unei
funcții în timp ce aceasta este în curs de execuție. Această tehnică, denumită încărcare leneșă sau
definiție leneșă, este una la care vom apela adesea în capitolele 9 și 10.
Deoarece funcțiile native, cum ar fi Object.create(), sunt compilate în limbajul de specialitate, acestea
rulează mult mai rapid decât funcțiile în text simplu. Așadar, cel mai bine este să optați pentru o funcție
nativă pentru a face o anumită muncă, dacă este disponibilă una. Încărcarea anticipată condiționată este o
modalitate de a vă asigura că JavaScript optează pentru gobbledygook cu execuție rapidă. Încărcarea leneșă
- și anume, ca o funcție să se redefinească singură la prima apelare - este o altă modalitate.
Încărcătoarele leneșe sunt adecvate pentru funcțiile care nu sunt necesare sau care nu sunt
necesare imediat. Lazy se referă la faptul că nu redefiniți o funcție decât dacă sau până când este
necesar. Pe de altă parte, încărcarea anticipată condiționată este adecvată pentru funcțiile de care aveți
neapărat nevoie, în special pentru cele care sunt necesare imediat.
În capitolul 5, am scris următoarea funcție clone() pentru a implementa moștenirea prototipală:
var clone = function (donor) { var
Proxy = function () {};
Proxy.prototype = donor;
return new Proxy();
};
Dacă omiteți al doilea parametru opțional, Object.create() face același lucru ca și clone(), dar mult
mai rapid. În măsura în care descriptorii sunt prea greoi pentru a adăuga membri care pot fi scriși,
enumerați și configurați, adică la fel ca cei adăugați cu ajutorul operatorului =, de cele mai multe ori
veți omite al doilea parametru. Ținând cont de acest lucru, haideți să refacem clone() într-un încărcător
leneș care optează pentru Object.create() în Explorer 9, Firefox 4 și alte browsere care cunosc
ECMAScript 5.
Începeți prin a pune definiția noastră de clonă în clauza else a unei condiții if care determină
dacă Object.create este definit. Cu toate acestea, omiteți cuvântul cheie var, deoarece dorim să
suprascriem funcția clone() care o conține, nu să creăm o funcție clone() imbricata.
var clone = function (donor) {
if (Object.create !== undefined) {
} else {
clone = function (donor) {
var Proxy = function () {};
Proxy.prototype = donor;
return new Proxy();
};
}
};
Acum, în cadrul clauzei if, returnați pur și simplu obiectul gol creat prin trecerea donor la
Object.create():
var clone = function (donor) {
if (Object.create !== undefined) {
clone = function (donor) { return
Object.create(donor);
};
} else {
clone = function (donor) { var
Proxy = function () {};
Proxy.prototype = donor;
195
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
196
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
return Object.create(donor);
};
} else {
clone = function (donor) { var
Proxy = function () {};
Proxy.prototype = donor;
return new Proxy();
};
}
return clone(donor);
};
var banana = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [1, " ceașcă", "Organic
Valley"], zahăr: [9/16, "ceașcă"],
gălbenușuri: [3],
banană: [1 + 1/2, "cană, piure"], lapte
de cocos: [1/4, "cană"],
lămâie: [2, "linguriță", " lămâie Meyer proaspăt
stoarsă"], vanilie: [1, "boabă", "Madagascar
Bourbon"]].
};
var chunkyMonkey = clone(banana);
chunkyMonkey.walnuts = [ 3/4, "ceașcă, mărunțită grosier"];
chunkyMonkey.bittersweet = [1, "ceașcă, mărunțită grosier",
"Callebaut"]; console.dir(banana);
consola.dir(chunkyMonkey);
197
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Figura 6-3. Firefox 4 creează chunkyMonkey cu Object.create(), în timp ce Firefox 3 creează chunkyMonkey
cu Proxy().
Recursiune
În timp ce expresiile literale ale funcțiilor creează valori de funcție, expresiile de invocare a funcțiilor
creează valori de orice tip, de obicei prin manipularea uneia sau mai multor valori denumite parametri
sau argumente. În măsura în care o valoare de funcție se poate autoinvoca, o funcție poate lucra asupra
parametrilor și apoi să și-i transmită înapoi. Procedând astfel, denumită recursivitate, oferă o
modalitate de a face o mulțime de muncă amețitoare în pași mici. Funcțiile recursive sunt de neprețuit
pentru traversarea DOM, lucru pe care îl vom explora în capitolul 7. Dar am scris deja o funcție
recursivă în Capitolul 5. Acolo am transformat un litru de Ben & Jerry's Coffee Heath Bar Crunch din
Vanilla Heath Bar Crunch prin simpla clonare a membrilor cu o funcție recursivă numită
cloneMembers(), astfel:
var cloneMembers = function cloneMembers (donor, d o n e e ) {
donee = donee || {};
for (var m in donor) {
if (donor.hasOwnProperty(m)) {
if (typeof d o n o r [ m] === "object" && donor[m] !== null) {
donee[m] = typeof donor[m].pop === "function" ? [] : {};
cloneMembers(donor[m], donee[m]);
} else {
donatar[m] = donator[m];
}
}
198
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
}
retur donatar;
};
var vanillaHeathBarCrunch = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
heathBars: [4, "batoane, tocate grosier"],
vanilie: [1, "boabă", "Madagascar Bourbon"]]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);
coffeeHeathBarCrunch.coffee = [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
Observați modul în care testăm dacă un obiect este un array verificând dacă are o metodă pop().
Vom aborda array-urile și metodele lor mai târziu în acest capitol, dar deocamdată rețineți că array-
urile au o metodă pop() de tip funcție.
Dacă am rescrie cloneMembers() ca o funcție nerecuzivă, adică cloneMembers(donor[m],
donee[m]);; atunci ar trebui să invocăm cloneMembers() de opt ori în loc de o dată. Așadar, recursivitatea
ne scutește de a fi nevoiți să tastam în felul următor, care, după cum arată figura 6-4, funcționează în
continuare bine.
var cloneMembers = function cloneMembers (donor, d o n e e ) {
donee = donee || {};
for (var m in donor) {
if (donor.hasOwnProperty(m)) {
if (typeof d o n o r [ m] === "object" && donor[m] !== null) {
donee[m] = typeof donor[m].pop === "function" ? [] : {};
} else {
donatar[m] = donator[m];
}
}
}
retur donatar;
};
var vanillaHeathBarCrunch = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
heathBars: [4, "batoane, tocate grosier"],
vanilie: [1, "boabă", "Madagascar Bourbon"]]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);
coffeeHeathBarCrunch.heavyCream = cloneMembers(vanillaHeathBarCrunch.heavyCream,
coffeeHeathBarCrunch.heavyCream);
coffeeHeathBarCrunch.halfHalf = cloneMembers(vanillaHeathBarCrunch.halfHalf,
coffeeHeathBarCrunch.halfHalf);
coffeeHeathBarCrunch.sugar = cloneMembers(vanillaHeathBarCrunch.sugar,
coffeeHeathBarCrunch.sugar);
coffeeHeathBarCrunch.yolks = cloneMembers(vanillaHeathBarCrunch.yolks,
coffeeHeathBarCrunch.yolks);
coffeeHeathBarCrunch.heathBars = cloneMembers(vanillaHeathBarCrunch.heathBars,
199
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
cafeaHeathBarCrunch.heathBars);
coffeeHeathBarCrunch.vanilla = cloneMembers(vanillaHeathBarCrunch.vanilla,
coffeeHeathBarCrunch.vanilla);
coffeeHeathBarCrunch.heavyCream = cloneMembers(vanillaHeathBarCrunch.heavyCream,
coffeeHeathBarCrunch.heavyCream);
coffeeHeathBarCrunch.coffee = [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
console.dir(vanillaHeathBarCrunch);
consola.dir(coffeeHeathBarCrunch);
Figura 6-4. Recursivitatea ne împiedică să invocăm cloneMembers() de opt ori în loc de o singură dată.
Nu știu ce părere aveți voi, dar eu prefer să lucrez inteligent decât să muncesc din greu. Așadar,
recursivitatea este un lucru de păstrat. Rețineți că în capitolul 7 vom scrie o funcție recursivă numită
traverseTree() pentru a parcurge arborele DOM. Făcând acest lucru, este posibil să evităm să fim
nevoiți să invocăm traverseTree() de sute de ori manual.
Un alt mod de a implementa recursivitatea este prin intermediul arguments.callee, care se referă
la funcția în curs de execuție. Vom folosi această abordare puțin mai târziu în carte:
var cloneMembers = function cloneMembers (donor, d o n e e ) {
donee = donee || {};
for (var m in donor) {
if (donor.hasOwnProperty(m)) {
200
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
• Argumentele funcției sub forma unei serii de valori separate prin virgulă, în
cazul lui call()
Figura 6-7. Constructorii nativi JavaScript definiți de ECMAScript sau DOM suprascriu întotdeauna
Object.prototype.toString().
Object.prototype.toString.apply(wildMaineBlueberry.halfHalf);
204
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
// "[object Array]"
Object.prototype.toString.call(wildMaineBlueberry.freshLemonJuice);
// "[object Array]"
({}).toString.apply(wildMaineBlueberry.blueberries);
// "[object Array]"
({}).toString.call(wildMaineBlueberry.vanilla);
// "[object Array]"
OK, cu apply() și call() în minte, putem scrie acum un încărcător condiționat în avans, așa cum se
arată în continuare. Rețineți că este necesar să înfășurați un literal de obiect gol în paranteze doar
atunci când începe o linie de cod (pentru a preveni confuzia dacă este vorba de un obiect sau de un
bloc), așa că le putem omite aici.
Verificați câteva valori cu Array.isArray(), comparând munca dumneavoastră cu cea din figura 6-9.
D a c ă executați Firefox 4, JavaScript va folosi funcția nativă ECMAScript 5, dar, dacă executați
Firefox 3, va folosi o copie a noastră.
if (Array.isArray === undefined) {
Array.isArray = function(v) {
return {}.toString.apply(v) === "[object Array]";
};
}
var WildMaineBlueberry = function(afine, vanilie) {
this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
heavyCream: [1, "cup", "Organic Valley"],
halfHalf: [1, " c e a ș c ă ", "Organic Valley"],
zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
freshLemonJuice: [2, "tsp"],
205
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Array.isArray(wildMaineBlueberry.halfHalf);
// true
Array.isArray(wildMaineBlueberry.halfHalf[2]);
// fals
Figura 6-9. Verificarea caracterului de array cu metoda nativă Array.isArray() sau cu imitația noastră
Rescrierea cloneMembers()
Acum că avem o modalitate mai bună de a verifica dacă o metodă precum pop() sau slice() este
definită, să refacem cloneMembers() în consecință. Apoi, încercați să fabricați un sfert de Coffee
Heath Bar Crunch prin clonarea și mărirea unui sfert de Vanilla Heath Bar Crunch, verificând munca
dvs. cu ajutorul figurii 6-10:
if (Array.isArray === undefined) {
Array.isArray = function(v) {
return {}.toString.apply(v) === "[object Array]";
};
}
var cloneMembers = function (donor, donee) {
donee = donee || {};
for (var m in donor) {
if (donor.hasOwnProperty(m)) {
if (typeof donor[m] === "object" && donor[m]== null) {
donee[m] = Array.isArray(donor[m]) ? [] : {};
cloneMembers(donor[m], donee[m]);
} else {
206
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
donatar[m] = donator[m];
}
}
}
retur donatar;
};
var vanillaHeathBarCrunch = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6],
heathBars: [4, "batoane, tocate grosier"],
vanilie: [1, "boabă", "Madagascar Bourbon"]]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);
coffeeHeathBarCrunch.coffee = [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];
console.dir(vanillaHeathBarCrunch);
consola.dir(coffeeHeathBarCrunch);
Figura 6-10. Bătaia unui litru de Coffee Heath Bar Crunch cu o funcție cloneMembers() îmbunătățită
207
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Currying
Deoarece atât funcțiile, cât și parametrii acestora sunt valori, putem crea o nouă valoare a funcției
prin combinarea unei funcții vechi și a unuia sau mai multor parametri, astfel încât acești parametri să
fie prestabiliți în noua funcție. Acest lucru este denumit currying în onoarea creatorului său, Haskell
Curry, care are și limbajul de programare Haskell numit după el.
ECMAScript 5 definește o nouă metodă pentru valorile funcțiilor, Function.prototype.bind(), care
este ideală pentru currying. Deși Explorer 9 și Firefox 4 vor implementa Function.prototype.bind, la data
scrierii acestui articol, Explorer 8, Firefox 3 și alte browsere pre-ECMAScript 5 nu o fac. Așadar, chiar
și atunci când va sosi cavaleria, tot va trebui să emulăm Function.prototype.bind().
Haideți să ne suflecăm mânecile și să facem acest lucru cu ajutorul încărcătoarelor noastre
anticipate condiționate pentru Object.defineProperties() și Object.create(). Aceste două
încărcătoare anticipate condiționate au fost implementate cu o instrucțiune if. Dar dacă nu este
singura modalitate de a scrie un încărcător anticipat condiționat - și anume
Funcționează și operatorii || și ?:. Prin urmare, haideți să alegem o valoare pentru
Function.prototype.bind() cu || de data aceasta.
Amintiți-vă din capitolul 3 că, dacă primul operand al expresiei || este fals, adică evaluează "", 0,
NaN, false, nedefinit sau null, atunci expresia || în ansamblu evaluează al doilea operand. Așadar, dacă
interogarea Function.prototype.bind returnează undefined, așa cum ar face-o orice browser pre-
ECMAScript 5, atunci expresia noastră || va fi evaluată la al doilea operand, care va fi emulația
noastră de Function.prototype.bind(). Deocamdată, faceți doar un literal gol care funcționează cu un
parametru obj:
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
}
}
};
}
if (Object.create === undefined) {
Object.create = function (parent, descriptors) { var
Proxy = function () {},
copil;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
Function.prototype.bind = Function.prototype.bind ||
function (obj) {
};
Acum, cuvântul cheie this se va referi la funcția care moștenește metoda bind(). O vom salva într-
o variabilă numită that, astfel încât să o putem folosi pentru a construi noua funcție. În mod
tradițional, atunci când doriți să salvați acest lucru, o faceți într-o variabilă numită that. Dar nu
trebuie să faceți acest lucru. La fel ca în Haskell Curry, dacă doriți, puteți da numele dvs. variabilei.
if (Object.defineProperties === undefined) {
208
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
209
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
210
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
211
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
fn = function () {
return that.apply(this instanceof that ? this : obj,
ossify.concat([].slice.call(arguments, 0)));
};
fn.prototype = Object.create(that.prototype);
return fn;
};
212
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Figura 6-11. WinterWildMaineBlueberry() este creat prin combinarea WildMaineBlueberry() și a aplicației sale
afine și parametri de lămâie.
Metode de înlănțuire
În cazul în care salvați o funcție într-un obiect, funcția este denumită metodă. În plus, în corpul
metodei, aceasta se referă la obiectul în care ați salvat metoda. Astfel, atunci când apelați o funcție ca
un constructor, aceasta se referă la obiectul pe care îl returnează constructorul, dar atunci când apelați
o funcție ca metodă, aceasta se referă la obiectul care conține funcția. În cele din urmă, dacă apelați o
funcție în mod tradițional ca metodă globală, atunci aceasta se referă la fereastra obiectului global.
Cu toate acestea, ECMAScript 5 schimbă valoarea acestui obiect din fereastră în null pentru a evita să
aveți probleme dacă uitați să invocați un constructor cu new.
Oricum, pentru a ilustra ideea, să folosim Object.create() pentru a crea un obiect numit iceCream
care moștenește metodele _french(), _vanilla() și _coffee() de la un alt obiect numit churn. Apoi,
213
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
dacă invocăm aceste metode pe iceCream, aceasta va face referire la iceCream și va popula membrii
iceCream. Prin urmare, așa cum arată figura 6-12, iceCream va conține membrii heavyCream, halfHalf,
sugar, yolks, vanilla și coffee, pe care îi putem afișa prin apelarea _print():
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
}
}
};
}
if (Object.create === undefined) {
Object.create = function (parent, descriptors) { var
Proxy = function () {},
copil;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
var churn = {};
churn._french = function (cremă grea, j u m ă t a t e , zahăr,
gălbenușuri) { this.heavyCream = [1, "ceașcă", cremă grea ||
"Organic Valley"], this.halfHalf = [1, "ceașcă", jumătate ||
"Organic Valley"], this.sugar = [zahăr || 5/8, "ceașcă"],
this.gălbenușuri = [gălbenușuri || 6]
};
churn._vanilla = function (vanilla) {
this.vanilla = [1, "bean", vanilla || "Madagascar Bourbon"];
};
churn._coffee = function (coffee) {
this.coffee = [1/4, "ceașcă, măcinată grosier", cafea || "Starbucks Espresso"];
};
churn._print = function () {
var copy = {};
for (var m in this) {
this.hasOwnProperty(m) && (copy[m] = this[m]);
}
console.dir(copy);
};
var iceCream = Object.create(churn);
iceCream._french();
iceCream._vanilla();
iceCream._coffee();
iceCream._print();
214
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Figura 6-12. aceasta se referă la obiectul care conține funcțiile invocate ca metode.
Totul a funcționat bine, dar există o modalitate mai elegantă de a obține ceea ce tocmai am făcut,
prin înlănțuirea apelurilor de metode.
înghețată._franceză()._vanilie()._cafea()._imprimare();
Să vedem cum să activăm această tehnică.
În acest moment, metodele noastre returnează undefined, dar pentru a lega o metodă de o alta,
trebuie să returnăm acest lucru. După cum arată figura 6-13, acest lucru funcționează la fel de bine,
dar mai elegant decât invocarea separată a metodelor. Rețineți că înlănțuirea metodelor este, de
asemenea, denumită cascadă. Rețineți, de asemenea, că înlănțuirea este foarte frecventă în bibliotecile
DOM și JavaScript.
var clone = typeof Object.create === "function" ?
Object.create :
funcție (donator) {
var Proxy = function () {};
Proxy.prototype = donor;
215
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Să trecem la JavaScript pentru a vedea ce putem face cu toate aceste informații. În primul rând,
câteva informații de bază: orice variabilă pe care o declarați între acoladele curly care înfășoară blocul
unui if, for, while sau altă instrucțiune compusă este vizibilă în afara blocului, după cum ați văzut pe
parcursul acestei cărți. Altfel spus, parantezele curly nu oferă o modalitate de a crea variabile private.
În schimb, funcțiile au domeniul de aplicare al funcțiilor, ceea ce înseamnă că orice variabile
sau funcții declarate în interiorul parantezelor curbe care înfășoară un bloc de funcții nu sunt în mod
normal vizibile în afara blocului. Să aflăm de ce.
Ori de câte ori definiți o funcție, JavaScript salvează lanțul de domenii de aplicare ca parte a noului
obiect al funcției; cu alte cuvinte, lanțul de domenii de aplicare este o secvență de obiecte, începând cu
obiectul de apelare al funcției și terminând cu obiectul global al ferestrei. Acest lucru înseamnă că această
parte a lanțului de domeniu de aplicare este stabilită în piatră înainte ca funcția să fie executată. Cu toate
acestea, toate variabilele, funcțiile sau argumentele conținute de obiectele de apel și globale care alcătuiesc
acest lanț de domeniu de aplicare sunt active.
Apoi, atunci când invocați o funcție, JavaScript adaugă orice variabile definite local în cadrul funcției,
parametrii numiți, obiectul argumentelor și acest lucru la lanțul domeniului de aplicare. Astfel, aceste
obiecte variabile vor fi diferite de fiecare dată când veți invoca funcția. JavaScript se uită în acest set
complet de elemente din lanțul domeniului de aplicare atunci când caută valoarea unei variabile. Cu
alte cuvinte, atunci când invocați o funcție care utilizează o variabilă, JavaScript poate utiliza variabila
numai dacă aceasta este declarată în lanțul de domeniu de aplicare.
În mod normal, după ce o funcție invocată se întoarce, tot ceea ce a fost adăugat în lanțul de
domenii de aplicare atunci când ați invocat funcția este distrus. Cu toate acestea, dacă creați o
închidere, obiectele conținute în domeniul de aplicare al funcției nu sunt distruse. Prin urmare, puteți
interoga parametrii numiți și variabilele definite la nivel local pentru acea invocare chiar și după ce
aceasta s-a încheiat. Să ne uităm acum la închideri.
În măsura în care închiderea este o tehnică extrem de populară, faceți-vă o favoare și mergeți cu
zece degete în timp ce noi explorăm aceste tehnici acum. Să zicem că dorim să salvăm niște valori
implicite pentru ciocolată dulce-amăruie, cacao și vanilie într-o închidere pe care constructorul nostru
ChocolateChocolate() o poate interoga. O modalitate ar fi să le definim ca variabile locale pentru o
funcție care se invocă singură, iar valoarea de retur a acesteia să fie constructorul
ChocolateChocolate().
Așadar, în următorul exemplu, chocolateChocolate este transformat cu cacao Callebaut și vanilie
Bourbon din Madagascar datorită închiderii, după cum arată figura 6-14, împreună cu funcția pe care
JavaScript o atribuie lui ChocolateChocolate.
var ChocolateChocolate = function () {
var _bittersweet = "Ghirardelli",
_cocoa = "Callebaut",
_vanilla = "Madagascar Bourbon";
return function (bittersweet, c a c a o , vanilie) {
this.bittersweet = [1, "cup", bittersweet || _bittersweet];
this.cocoa = [3, "tbs", cocoa || _cocoa];
this.vanilla = [1, "bean", vanilla || _vanilla];
};
}();
ChocolateChocolate.prototype = { heavyCream:
[1, "cup", "Organic Valley"], halfHalf: [1,
" c e a ș c ă ", "Organic Valley"], zahăr: [5/8,
"ceașcă"],
gălbenușuri: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
218
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
O altă modalitate de a salva valorile implicite pentru ciocolata dulce-amăruie, cacao și vanilie într-o
închidere ar fi să se definească parametrii _bittersweet, _cocoa și _vanilla pentru funcția de auto-
invocare care returnează constructorul ChocolateChocolate(). Apoi, se transmit valorile lor funcției
de auto-invocare.
Așadar, după cum arată figura 6-15, salvarea valorilor noastre implicite ca parametri numiți pentru
o închidere funcționează la fel de bine ca și salvarea acestora ca variabile locale pentru închidere.
Observați că definiția lui ChocolateChocolate() este aceeași ca în exemplul anterior. Motivul pentru care
nu a trebuit să modificăm definiția ChocolateChocolate() este că nu există nicio diferență între
parametrii numiți și variabilele locale pe un obiect de activare. Altfel spus, parametrii numiți devin
variabile locale. Singura diferență între cele două este modul în care le atribuiți o valoare.
var ChocolateChocolate = function ( _bittersweet, _cocoa, _vanilla) {
return function (bittersweet, cacao, vanilie) {
this.bittersweet = [1, "cup", bittersweet || _bittersweet];
this.cocoa = [3, "tbs", cocoa || _cocoa];
this.vanilla = [1, "bean", vanilla || _vanilla];
};
}("Ghirardelli", " Callebaut", "Madagascar Bourbon");
ChocolateChocolate.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
219
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Un al treilea mod de a salva valorile implicite pentru ciocolata dulce-amăruie, cacao și vanilie într-o
închidere ar fi să declarați mai întâi variabila ChocolateChocolate și apoi să îi atribuiți o funcție din
cadrul funcției de auto-invocare. Altfel spus, exportați o funcție definită la nivel local către o variabilă
globală, ca în exemplul următor și în figura 6-16. Rețineți că, la fel ca în cele două exemple anterioare,
nu a fost nevoie să schimbăm definiția constructorului ChocolateChocolate(). Diferă doar modul în
care creăm o închidere pentru ca acesta să fie interogat. Observați, de asemenea, că am înfășurat
funcția de auto-invocare între paranteze. Acestea sunt obligatorii în măsura în care dorim ca JavaScript
să interpreteze funcția ca pe o expresie de funcție și nu ca pe o declarație de funcție. Adică, pentru a
preveni o eroare de sintaxă.
var ChocolateChocolate = null;
(function (_bittersweet, _cocoa, _vanilla) {
ChocolateChocolate = function (bittersweet, cacao, vanilie) {
this.bittersweet = [1, "cup", bittersweet || _bittersweet];
this.cocoa = [3, "tbs", cocoa || _cocoa];
this.vanilla = [1, "bean", vanilla || _vanilla];
};
}("Ghirardelli", " Callebaut", "Madagascar Bourbon")));
ChocolateChocolate.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
220
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Exemplul anterior ar putea fi refăcut pentru a utiliza variabilele _bittersweet, _cocoa și _vanilla
definite la nivel local, mai degrabă decât parametrii numiți _bittersweet, _cocoa și _vanilla. Încercați-
o, verificând munca dvs. cu ajutorul figurii 6-17.
var ChocolateChocolate = null;
(function () {
var _bittersweet = "Ghirardelli",
_cocoa = "Callebaut",
_vanilla = "Madagascar Bourbon";
ChocolateChocolate = function (bittersweet, cacao, vanilie) {
this.bittersweet = [1, "cup", bittersweet || _bittersweet];
this.cocoa = [3, "tbs", cocoa || _cocoa];
this.vanilla = [1, "bean", vanilla || _vanilla];
};
}());
ChocolateChocolate.prototype = {
heavyCream: [1, "cup", "Organic Valley"],
halfHalf: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
221
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Figura 6-17. Refacerea eșantionului anterior pentru a utiliza _bittersweet, _cocoa și _vanilla definite la nivel local
mai degrabă decât parametrii numiți _bittersweet, _cocoa și _vanilla
În cele din urmă, rețineți că, în loc să înfășurați auto-invocarea în paranteze, veți vedea adesea
expresia funcției înfășurată în paranteze, care sunt apoi urmate de operatorul (), ca în exemplul
următor. Încercați-l, verificând munca dvs. cu ajutorul figurii 6-18:
var ChocolateChocolate = null;
(function (_bittersweet, _cocoa, _vanilla) {
ChocolateChocolate = function (bittersweet, cacao, vanilie) {
this.bittersweet = [1, "cup", bittersweet || _bittersweet];
this.cocoa = [3, "tbs", cacao || _cocoa];
this.vanilla = [1, "bean", vanilla || _vanilla];
};
})("Ghirardelli", " Callebaut", "Madagascar Bourbon");
ChocolateChocolate.prototype = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [1, " c e a ș c ă ", "Organic
Valley"], zahăr: [5/8, "ceașcă"],
gălbenușuri: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
222
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE
Figura 6-19. Definirea unui obiect de configurație în locul mai multor parametri defaut
Funcții de apelare
În măsura în care funcțiile sunt valori, puteți transmite o funcție ca parametru unei alte funcții, care o
poate apoi invoca. O funcție transmisă și invocată în acest mod este denumită funcție de rechemare.
Funcțiile de ascultare a evenimentelor, pe care le vom explora în capitolul 9, sunt cel mai comun tip de
funcție de rechemare. Dar ele nu sunt singura modalitate de a implementa acest model.
De exemplu, am putea să refacem funcția noastră de clonare de la începutul capitolului, astfel
încât să putem să-i transmitem fie un obiect, fie o funcție de constructor callback. Așa că haideți să
facem acest lucru acum, iar apoi să testăm ambele variante prin
224