Sunteți pe pagina 1din 458

THE EXPERT'S VOICE® ÎN DEZVOLTAREA WEB

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

Drepturi de autor © 2010 de Terry McNavage


Toate drepturile rezervate. Nici o parte din această lucrare nu poate fi reprodusă sau transmisă sub
nicio formă sau prin niciun mijloc, electronic sau mecanic, inclusiv prin fotocopiere, înregistrare sau
prin orice sistem de stocare sau recuperare a informației, fără permisiunea prealabilă scrisă a
proprietarului drepturilor de autor și a editorului.
ISBN-13 (pbk): 978-1-4302-7219-9
ISBN-13 (electronic): 978-1-4302-7218-2
Tipărit și legat în Statele Unite ale Americii 9 8 7 7 6 5 4 3 2 1

Î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ă.

Utilizarea în această publicație a denumirilor comerciale, a mărcilor comerciale, a mărcilor de servicii


și a termenilor similari, chiar dacă nu sunt identificate ca atare, nu trebuie interpretată ca o exprimare a
unei opinii cu privire la faptul că acestea fac sau nu obiectul unor drepturi de proprietate.
Președinte și editor: Paul Manning
Redactori principali: Ben Renow-Clarke, Matthew Moodie
Revizori tehnici: Kristian Besley, Rob Drimmie, Tom Barker
Comitetul editorial: Steve Anglin, Mark Beckner, Ewan Buckingham, Gary Cornell, Jonathan
Gennick, Jonathan Hassell, Michelle Lowman, Matthew Moodie, Duncan Parkes, Jeffrey Pepper,
Frank Pohlmann, Douglas Pundick, Ben Renow-Clarke, Dominic Shakeshaft, Matt Wade, Tom
Welsh.
Editor coordonator: Mary Tobin
Redactor: Kim Wimpsett
Compozitor: MacPS, LLC
Indexator: Toma Mulligan
Designer de copertă: Anna Ishchenko
Distribuit în întreaga lume de Springer Science+Business Media, LLC, 233 Spring Street, 6th Floor, New York,
NY 10013. Telefon 1-800-SPRINGER, fax (201) 348-4505, e-mail orders-ny@springer- sbm.com, sau vizitați
www.springeronline.com.
Pentru informații despre traduceri, vă rugăm să trimiteți un e-mail la rights@apress.com sau să vizitați
www.apress.com.
Cărțile Apress și ale prietenilor de la ED pot fi achiziționate în vrac pentru uz academic, corporativ sau
promoțional. Versiunile și licențele pentru cărți electronice sunt, de asemenea, disponibile pentru
majoritatea titlurilor. Pentru mai multe informații, consultați pagina noastră web Special Bulk Sales-eBook
Licensing la www.apress.com/info/bulksales.

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

■ Cuprins dintr-o privire .....................................................................................iv


■ Despre autor .................................................................................................xiii
■ Despre evaluatorii tehnici...............................................................................xiv
■ Recunoștințe..............................................................................................xv
■ Prefață .......................................................................................................xvi

■ Capitolul 1: Reprezentarea datelor cu valori........................................................1


Ce sunt tipurile de valori?......................................................................................1
Crearea unui literal de șir de caractere ..................................................................2
Comentariul Codului ....................................................................................................................2
Lipirea șirurilor de caractere cu ajutorul operatorului +.....................................................................3

Crearea unui număr literal ...................................................................................4


Crearea unui literal boolean.................................................................................5
Numirea unei valori cu un identificator ...................................................................6
Pot să numesc o variabilă cum vreau eu? ..........................................................................................6
Unii identificatori valabili sunt deja ocupați .....................................................................................7

Crearea unui obiect literal ...................................................................................9


Numirea membrilor cu identificatori .............................................................................................12
Crearea unui Literal Array .................................................................................14
Crearea unei funcții literale................................................................................19
Rezumat......................................................................................................23

v
www.allitebooks.com
■ CUPRINS

■ Capitolul 2: Conversia de tip............................................................................25


Membrii String................................................................................................25
Determinarea numărului de caractere..........................................................................................30
Decodarea sau codificarea caracterelor..........................................................................................31
Caz de conversie.......................................................................................................................33
Localizarea unui subșir ..............................................................................................................35
Tăierea unui subșir....................................................................................................................36
Înlocuirea unui subșir ..................................................................................................................37
Împărțirea unui șir de caractere într-o matrice de șiruri mai mici .....................................................39
Căutarea cu expresii regulate.....................................................................................................43

Crearea explicită a înfășurătoarelor.....................................................................43


Conversia unei valori în alt tip ............................................................................44
Conversia unei valori într-un număr ...............................................................................................46
Conversia unei valori într-un șir de caractere ...................................................................................50
Amânarea învățării sintaxei RegExp ..............................................................................................53

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

Încatenare || Expresii ..................................................................................................................85


Expresii && și înlănțuire...............................................................................................................87
Încatenarea expresiilor || și &&....................................................................................................89
Returnarea condiționată a uneia dintre cele două valori ....................................................................90
Efectuarea a două expresii ca fiind una ..........................................................................................93
Ștergerea unui membru, element sau variabilă .............................................................................94

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

Luarea uneia dintre cele mai multe căi cu un comutator........................................107


Scrierea unei bucle while ................................................................................115
Abandonarea unei iterații, dar nu și a buclei...............................................................................118
Înlocuirea Break cu Return într-o funcție....................................................................................120

Scrierea unei bucle do while ............................................................................122


Scrierea unei bucle for....................................................................................125
Enumerarea membrilor cu o buclă for in.............................................................127
Condiționale mai rapide ................................................................................129
Bucle Snappier Loops...................................................................................136
Rezumat....................................................................................................144
■ Capitolul 5: Moștenirea membrilor .................................................................145
Crearea obiectelor cu un constructor .................................................................145
Moștenirea clasică .......................................................................................149
Determinarea tipului sau a tipurilor de care un obiect este o instanță..................................................156
Membrii moșteniți sunt partajați, nu copiate ...............................................................................158
Modificarea instanțelor noi și anterioare ale unui tip....................................................................160
www.allitebooks.com
vii
■ CUPRINS

Împărtășirea unui prototip, dar renunțarea la lanț........................................................................163


Adăugarea unei verigi de lanț goale..............................................................................................166
Furtul unui constructor .............................................................................................................169

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

Încărcare leneșă ...........................................................................................194


Recursivitate .............................................................................................198
Împrumut de metode cu apply() sau call() ..........................................................201
Suprascrierea lui toString() ......................................................................................................201
Testarea pentru o matrice ........................................................................................................204
Rescrierea cloneMembers()...................................................................................................206
Currying ....................................................................................................208
Metode de înlănțuire......................................................................................212
Funcții de închidere și de returnare ...................................................................216
Transmiterea unui obiect de configurare ............................................................222
Funcții de apelare ........................................................................................223
Memoizare ................................................................................................224
viii

www.allitebooks.com
■ CUPRINS

Reducerea globală cu ajutorul modulelor............................................................226


Array-uri ....................................................................................................228
Extragerea elementelor dintr-o matrice......................................................................................229
Adăugarea de elemente la o matrice..........................................................................................233
Lipirea a două rețele între ele......................................................................................................235
Inversarea elementelor dintr-o matrice.......................................................................................237
Sortarea elementelor dintr-o matrice .........................................................................................238
Crearea unui șir de caractere dintr-o matrice..............................................................................243
Preluarea unei porțiuni dintr-o matrice .......................................................................................244
Conversia unui obiect de tip tablou numai pentru citire într-un tablou ............................................245
Inserarea sau ștergerea elementelor dintr-o matrice....................................................................249

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

Interogarea atributelor ca un membru . 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111281


Interogarea atributelor cu ajutorul metodelor .
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111282
Interogarea nodurilor Atr .1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111285
Enumerarea atributelor pentru un element. 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111286
Creareadenodurideelementsaudetext.11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111288
DeletingContent.111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111292
Copierea conținutulu.1i111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111293

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

Descărcarea fișierelor de probă . 111111111111111111111111111111111111111111111111111111111111111111111111111111111310


Interogarea unui atribut de stil .
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111313 Scripting Classes .
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111318
Reguli de scripting . 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111320
Scriptingul foilor de stil importate . 1111111111111111111111111111111111111111111111111111111111111111111111111111111326
Adăugarea sau ștergerea unei reguli .
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111327Adăugareauneiregulilaofoaiedestil.
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111328Ștergereauneiregulidintr-ofoaiedestil.
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111332
Interogarea stilurilor generale din cascadă . 1111111111111111111111111111111111111111111111111111111111111111334
Activarea și dezactivarea foilor de stil . 111111111111111111111111111111111111111111111111111111111111111111111111338
Includerea sau importarea foilor de stil .
11111111111111111111111111111111111111111111111111111111111111111111111111339 Integrarea unei foi de stil .
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111344 Summary.
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111345

x
■ CUPRINS

■ Capitolul 9: Ascultarea evenimentelor ............................................................347


Lucrul cu obiectul eveniment............................................................................347
Descărcarea fișierelor de proiect.......................................................................348
Încărcare condiționată avansată.......................................................................351
Spunându-i lui JavaScript să nu mai asculte pentru un eveniment ..........................353
Împiedicarea desfășurării acțiunilor în caz de neplată ...........................................353
Împiedicarea unui eveniment de la parcurgerea arborelui DOM ..............................355
Scrierea funcțiilor ajutătoare ............................................................................356
Răscolirea arborelui DOM ..........................................................................................................356
Găsirea unui element după clasă..............................................................................................358
Testarea pentru getElementsByClassName() .............................................................................360
Interogarea cascadei.................................................................................................................362
Sliding Sprites ..............................................................................................364
Pregătirea terenului .................................................................................................................365
Mutarea spritelor .....................................................................................................................368
Sprites mai rapide ...................................................................................................................370
Comportamentul Drag-and-Drop.......................................................................375
Scrierea ascultătorului de evenimente Mousedown .....................................................................375
Scrierea ascultătorului de evenimente Mousemove .........................................................................378
Scrierea ascultătorului de evenimente Mouseup .........................................................................380
Funcția auxiliară doZ()...............................................................................................................382
Pregătirea tragerii....................................................................................................................383
Schimbul de piei prin cheie..............................................................................390
Inițierea comportamentelor atunci când este disponibil arborele DOM .....................395
Combaterea răului global ................................................................................395
Rezumat....................................................................................................396
■ Capitolul 10: Scripting BOM ..........................................................................399
Descărcarea fișierelor de proiect.......................................................................399
Reamintirea datelor vizitatorilor cu ajutorul cookie-urilor........................................401
xi
■ CUPRINS

Obținerea preferințelor utilizatorului ..........................................................................................401


Setarea preferințelor de piele ale utilizatorului ............................................................................403
Setarea preferințelor utilizatorului..............................................................................................404

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

Despre evaluatorii tehnici

■ Kristian Besley (foto centru) este dezvoltator principal la Beetroot


Design (www.beetrootdesign.co.uk), unde dezvoltă aplicații web,
site-uri web, interacțiuni educaționale și jocuri scrise în principal în
diverse combinații de PHP, Flash și JavaScript.
Lucrează cu computere și cu internetul de mult prea mult timp.
De asemenea, petrece mult prea mult timp hackerind și dezvoltând
aplicații open source - inclusiv Moodle - pentru ca acestea să
funcționeze perfect.
Avertisment de sănătate: are o obsesie nesănătoasă de a-și face aplicațiile super compatibile cu RSS și
excesiv de configurabile.
Printre clienții săi trecuți și actuali se numără BBC, Pearson Education, Welsh Assembly Government
și o mulțime de clienți cu acronime precum JISC, BECTA, MAWWWFIRE și, probabil, preferatul său
(încercați să o spuneți cu voce tare), SWWWETN.
Atunci când nu lucrează, lucrează în altă parte, predând cursuri de media interactivă (la Gower
College- Swansea) sau oferind asistență tehnică la o întreagă gamă de instituții sau persoane fizice,
în încercarea de a le economisi timp și bani (pe cheltuiala sa!).
A fost autor și coautor al unui număr mare de cărți pentru prietenii de la ED și Apress, inclusiv seria
Foundation Flash, Flash MX Video, Flash ActionScript for Flash (cu minunatul David Powers) și Flash
MX Creativity. De asemenea, cuvintele sale au onorat de câteva ori paginile revistei Computer Arts.
Kristian locuiește în prezent cu familia sa în Swansea, Țara Galilor, și este un vorbitor fluent de galeză,
cu o pasiune pentru promovarea limbii pe web și în aplicațiile web bilingve, acolo unde este posibil.

■ 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

Figura 2. Descărcarea suplimentului Firebug

Acum că aveți Firefox și Firebug instalate, să vedem cum să lucrăm cu Firebug.


Firebug rulează codul JavaScript în raport cu orice document HTML încărcat în Firefox. Cu alte cuvinte,
trebuie să aveți un document HTML deschis în Firefox pentru ca Firebug să funcționeze.
În măsura în care ECMAScript nu oferă nici o modalitate de a manipula HTML sau CSS, în capitolele 1-
6 vom încărca pur și simplu următorul document HTML gol, firebug.html în descărcările de la
www.apress.com, în Firefox:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Firebug</title>
</head>
<body>
</body>
</html>
xvii
■ PREFAȚĂ

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 3. Apăsați F12 pentru a deschide sau închide Firebug.

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.

Figura 5. Activarea Firebug din meniul Console

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");

Figura 6. Introducerea unui singur rând în linia de comandă


xix
■ PREFAȚĂ

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.

Tabelul 1. Comenzi rapide de la tastatură pentru Firebug

Scurtătură Descriere Windows sau Linux Mac

Deschideți Firebug F12 F12

Închideți Firebug F12 F12

Treceți la fila Console Ctrl+Shift+L Comandă+Shift+L

Rulați codul în editorul de comenzi Ctrl+Return Comandă+Return

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":

Figura 10. Oops-JavaScript returnează un ReferenceError spunând "alrt nu este definit".


xxii
■ PREFAȚĂ

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
■■■

Reprezentarea datelor cu valori

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.

Ce sunt tipurile de valori?


În JavaScript, datele sunt reprezentate prin valori. Există patru tipuri de valori cu care se pot
transmite date: șir de caractere, număr, boolean și obiect. În plus, există două tipuri de valori cu care nu
se transmite nicio informație: undefined și null. Cele două moduri de a transmite "nimic acolo" nu vor
mai părea atât de ciudate în Capitolul 3.

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.

Crearea unui literal de șir de caractere


Un text simplu, cum ar fi înghețata mea preferată, Ben & Jerry's Chocolate Fudge Brownie, este
reprezentat cu o valoare de șir de caractere în JavaScript. Trebuie doar să înfășurați un text într-o pereche
de ghilimele duble sau simple și veți obține un șir de caractere.
În regulă, deschideți firebug.html în Firefox, apoi apăsați F12 pentru a activa Firebug. Dacă abia ați
venit la noi, întoarceți-vă la Prefață pentru detalii despre cum să faceți acest lucru. Tastați următorul
șir de caractere în panoul din dreapta al Firebug, apoi faceți clic pe Run. După cum afișează Figura 1-1,
JavaScript va reda valoarea șirului, imprimând-o în panoul din stânga al Firebug:
"Ben & Jerry's Chocolate Fudge Brownie";

Figura 1-1. JavaScript ne transmite literalul șirului de caractere ca un papagal.

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

Dar trebuie doar să introduceți și să executați următoarele:


" Ben & Jerry's"; "Chocolate
Fudge Brownie";
Dacă un exemplu de cod are două sau mai multe comentarii, acesta este un indiciu pentru a vă opri
și a face clic pe Run (Executare) pentru a verifica o valoare de returnare înainte de a introduce restul
exemplului.

Lipirea șirurilor de caractere cu ajutorul operatorului +


Pentru a lipi două șiruri de caractere, separați-le cu ajutorul operatorului de concatenare +. Vom explora
+ și o mulțime de alți operatori, enumerați aici, în Capitolul 3. Rețineți că valorile pe care le dați unui
operator pentru a lucra cu el sunt denumite operanzi.
[]
.
()
nou
++
--
!
delete
typeof
void
*
/
%
+
-
<
<=
>
>=
instanceof
în
===
!===
==
!=
&&
||
?:
=
*=
/=
%=
+=
-=
,
Faceți clic pe Clear (Ștergeți) în ambele panouri Firebug, apoi asamblați un șir mai mare din cinci șiruri mai
mici.
"Ben & Jerry's" + " " + " + "Chocolate Fudge Brownie" + " este înghețata mea preferată.".";
// "Ben & Jerry's Chocolate Fudge Brownie este înghețata mea preferată."
3
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI

Verificați munca dvs. cu figura 1-2.

Figura 1-2. Lipirea a cinci șiruri de caractere cu ajutorul operatorului +.

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.

Crearea unui număr literal


Scripturile fac de obicei multe calcule matematice. Deci, JavaScript, desigur, are un tip de valoare
numerică. Faceți clic pe Clear în Firebug și haideți ca JavaScript să facă niște calcule.
Chocolate Fudge Brownie are 4 porții per litru și 260 de calorii per porție. Așadar, am putea cere ca
JavaScript să calculeze caloriile pe halbă cu ajutorul operatorului *, care își înmulțește operanzii:
4 * 260;
// 1040
Sunt un pasionat de alergare, alergând zilnic aproximativ 14 mile de luni până sâmbătă. Duminica
fac 21 de kilometri. Am putea să-l punem pe JavaScript să calculeze kilometrii anual cu următoarea
expresie. Rețineți că / face împărțirea și + face adunarea. Rețineți, de asemenea, că JavaScript
evaluează mai întâi tot ce se află între paranteze.
(6 * 14 + 21) / 7 * 365;
// 5475
Este nevoie de aproximativ 100 de calorii pentru a alerga o milă, așa că, dacă ar fi să-mi alimentez
alergarea în întregime cu Chocolate Fudge Brownie, de câte halbe aș avea nevoie pe an? Rețineți că
Math.round() rotunjește un număr zecimal la un număr întreg. Deci, în cazul nostru, rotunjește
526.4423076923077 la 526. Math.round() este una dintre caracteristicile de manipulare a numerelor pe
care le vom explora în Capitolul 5. Rețineți, de asemenea, că + face adunare dacă ambii săi operanzi sunt
numere, dar concatenare dacă oricare dintre operanzi este un șir de caractere. Pentru ca acest lucru să
funcționeze, JavaScript convertește numărul 526 în șirul "526" înainte de a-l lipi de "pints of Chocolate
Fudge Brownie". Verificați munca dvs. cu ajutorul figurii 1-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

Figura 1-3. Efectuarea unor calcule cu numere

■ Notă Caracteristica de conversie a tipului de valoare din JavaScript este tratată mai pe larg în capitolul 2.

Crearea unui literal boolean


Uneori, veți dori un răspuns simplu, da sau nu, de la JavaScript. În aceste circumstanțe, valoarea de
returnare a unei expresii va fi adevărată pentru da și falsă pentru nu.
Faceți clic pe Clear în ambele panouri Firebug și întrebați JavaScript dacă Chocolate Fudge Brownie
este doar înghețată de ciocolată. Rețineți că operatorul === vă spune dacă două valori sunt identice:
"Chocolate Fudge Brownie" === "înghețată de ciocolată";
// fals
Asta e puțin spus. În regulă, acum să comparăm calculul anterior cu valoarea de întoarcere,
înainte de a verifica munca noastră cu ajutorul figurii 1-4:
Math.round((6 * 14 + 21) / 7 * 365 * 100 / (4 * 260)) + " halbe de Chocolate Fudge Brownie"
===
"526 de halbe de ciocolată c u ciocolată";
// adevărat

Figura 1-4. Operatorul === returnează întotdeauna un boolean.

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.

Numirea unei valori cu un identificator


În măsura în care valorile literale pe care le-am creat până acum sunt anonime, nu avem nicio
modalitate de a le interoga sau de a le manipula valorile ulterior. Pentru a remedia acest lucru, trebuie
să le numim cu un identificator. În acest fel, se creează o variabilă, care, desigur, este o valoare numită.
OK, faceți clic pe Clear în ambele panouri Firebug. Apoi tastați cuvântul cheie var urmat de
identificatorul iceCream și de punct și virgulă. Procedând astfel, declarați o variabilă numită
iceCream în JavaScript. Cu toate acestea, iceCream conține undefined, un literal care nu transmite
nicio valoare.
var iceCream;
Să punem șirul literal "Chocolate Fudge Brownie" în iceCream cu ajutorul operatorului =:
var iceCream;
iceCream = "Chocolate Fudge Brownie";
Pentru a interoga valoarea conținută de o variabilă, introduceți identificatorul acesteia. Introduceți iceCream
și faceți clic pe Run.
JavaScript va returna apoi șirul literal:
var iceCream;
iceCream = "Chocolate Fudge Brownie";
iceCream;
// "Chocolate Fudge Brownie"
Pentru a introduce o nouă valoare în iceCream, efectuați o altă operație =. Deci, să înlocuim "Chocolate Fudge
Brownie"
cu "New York Super Fudge Chunk", cam așa:
var iceCream;
iceCream = " Chocolate Fudge Brownie";
iceCream = "New York Super Fudge Chunk";
iceCream;
// "New York Super Fudge Chunk"

Pot să numesc o variabilă cum vreau eu?


Îmi pare rău, dar nu. Identificatorii JavaScript pot conține numai litere, numere și caracterul _
underscore. Totuși, nu poate începe cu un număr. În măsura în care identificatorii nu pot conține
spații albe, cei care conțin două sau mai multe cuvinte se scriu cu majuscule de cămilă. Cu alte
cuvinte, spațiile sunt eliminate, iar prima literă din fiecare cuvânt, cu excepția primului, este scrisă cu
majuscule. Așadar, newYorkSuperFudgeChunk reprezintă "New York Super Fudge Chunk" în majusculă
de cămilă.
Deși nu puteți numi o variabilă cum doriți, puteți introduce în ea orice expresie. Așadar, nu
sunteți limitat la literali. Faceți clic pe Clear (Ștergeți) în ambele panouri Firebug, apoi introduceți și
executați următoarele, înainte de a verifica acest lucru și cele câteva exemple anterioare cu Figura 1-5.
var newYorkSuperFudgeChunk = 4 * 300 + " calorii per pint";
newYorkSuperFudgeChunk;
// "1200 de calorii pe halbă"

6
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI

Figura 1-5. Crearea de variabile cu nume valide

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.

Unii identificatori valabili sunt deja ocupați


Sintaxa JavaScript, așa cum este definită de standardul ECMAScript, rezervă următorii identificatori,
denumiți cuvinte cheie. Acestea reprezintă cheia prin care JavaScript poate face ceva pentru
dumneavoastră. Așadar, termenul este potrivit. Numirea unei variabile cu un cuvânt cheie returnează
o eroare de sintaxă:
break
case
catch
continue
default
delete
do
else
finally
for
function
if
în
instanceof
new
return
switch
this
throw
try
typeof
var
void
while
with
7
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI

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

■ Notă Dacă sunteți curios în legătură cu standardul ECMAScript, vizitați http://www.ecmascript.org.

Crearea unui obiect literal


Tipul de valoare obiect vă oferă o modalitate de a crea un loc în memorie pentru valorile aferente, care
pot fi denumite cu un identificator sau un șir de caractere. Aceste valori conexe sunt denumite
membri. Așadar, spunem că un obiect conține membri.
În regulă, faceți clic pe Clear în ambele panouri Firebug. Apoi, creați un obiect literal gol numit
iceCream introducând o pereche de acolade, urmate, bineînțeles, de un punct și virgulă.
var iceCream = {
};
Adăugați acum un membru numit "Chocolate Fudge Brownie" urmat de o expresie cu numărul de
calorii pe halbă. La fel ca variabilele, membrii pot conține o valoare literală sau o expresie pentru o
valoare. Rețineți că numele membrului este separat de valoare prin două puncte.
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260
};
OK, acum membrii sunt separați prin virgulă. Deci, pentru a adăuga un al doilea membru, urmați-
l pe primul cu o virgulă, astfel:
var iceCream = {
"Chocolate Fudge Brownie": 4 * 260,
"Half Baked": 4 * 250
};
Acum mai sunt câțiva membri, astfel încât avem zece în total. Nu uitați să le separați
cu o virgulă. Dar nu urmați membrul final - "Misiune la Mărțișor" - cu o virgulă.
9
CAPITOLUL 1 ■ REPREZENTAREA DATELOR CU VALORI

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ă"

Figura 1-6. Interogarea unui membru în iceCream

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ă"

Figura 1-7. Scrierea unei noi valori într-un membru

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

"Chocolate Chip Cookie Dough": 4 * 270,


"Misiune la marțipan": 4 * 260
};
iceCream["Peanut Butter Cup"] = 4 * 360; iceCream["Peanut
Butter Cup"]] + " calorii per pint";
// "1440 de calorii pe halbă"

Figura 1-8. Adăugarea unui nou membru la un obiect

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

Numirea membrilor cu identificatori


Numirea membrilor iceCream cu șiruri de caractere ne-a permis să folosim spații albe, care sunt interzise
pentru identificatori. Dar am fi putut să folosim identificatori cu majusculă de cămilă, cum ar fi:
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
};
După ce am făcut acest lucru, acum putem interoga membrii cu operatorul . urmat de un identificator. Încercați
să faceți acest lucru
în Firebug, introducând și rulând următorul exemplu, verificând munca dvs. cu Figura 1-9.
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.newYorkSuperFudgeChunk + " calorii per pint";
// "1200 de calorii pe halbă"

Figura 1-9. Interogarea unui membru cu un identificator și nu cu un șir de caractere


13
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

Crearea unui Literal Array


Membrii din iceCream sunt codificați ca o listă de top 10. Cu toate acestea, nu există nicio modalitate de a
le interoga în JavaScript în acest mod. De exemplu, nu am putea întreba "Care este a treia aromă
preferată?". În plus, trebuie să numim membrii și să le dăm o valoare.
Așadar, dacă am dori să creăm pur și simplu o listă cu cele mai bune zece arome, omițând
detaliile deprimante privind caloriile, un obiect nu ar fi de ajuns. Pentru aceasta avem nevoie de o
matrice, care este un subtip al tipului de valoare obiect. Altfel spus, un array este tot un obiect, doar că
are câteva caracteristici suplimentare.
Una dintre aceste caracteristici este ordonarea numerică a valorilor cu numere întregi nenegative începând cu
0.
JavaScript face acest lucru în spatele scenei. Așadar, trebuie doar să enumerați valorile într-o matrice de
la primul la ultimul; JavaScript se ocupă de numerotare. Rețineți că valorile numerotate sunt denumite
elemente și nu membri.
14

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.

Figura 1-11. Crearea unui tablou și interogarea elementelor

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.

Figura 1-13. Interogarea unui element cu un șir de caractere și nu cu un număr

Î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

Figura 1-14. Obiectele pot avea și ele elemente.

În capitolul 5, vom explora unele caracteristici de manipulare a elementelor numai pentru


tablouri. Acestea fac ca elementele dintr-un array să fie magice în comparație cu cele dintr-un obiect.
Este ca și cum îmbrăcarea costumului de păianjen îl transformă pe Peter Parker obișnuit în
Spiderman.

Crearea unei funcții literale


În regulă, este banal să ne întrebăm ce aromă de înghețată aș clasifica pe locul al optulea:
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[7];
// "Cinnamon Buns"
Dar este cu totul altceva să te întrebi dacă o aromă precum "Cinnamon Buns" se află printre primele
zece, după cum ilustrează exemplul următor și figura 1-15:
var iceCream = [
"Chocolate Fudge Brownie",
"Half Baked",
"New York Super Fudge Chunk",
"Coffee Heath Bar Crunch",
"Cherry Garcia",
19
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

rankFlavor[9] = "Mission to Marzipan";


rankFlavor.len = 10;
rankFlavor("New York Super Fudge Chunk");
// "New York Super Fudge Chunk este numărul 3."
rankFlavor("Peanut Brittle");
// "Prăjitura de alune nu se află în top 10 al meu."

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

Figura 2-1. concat() funcționează ca și operatorul +.

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

Figura 2-2. Metodele String nu modifică șirul inițial.

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

Figura 2-4. Dar, de asemenea, este obișnuit să nu se salveze valoarea de întoarcere.

Î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:

• Puteți salva valoarea de returnare într-o nouă variabilă.

• Puteți înlocui șirul din variabila originală cu valoarea de returnare.

• Puteți utiliza imediat valoarea de returnare ca operand pentru un operator, cum ar fi


===.
Rețineți că primele două modalități se aplică și membrilor obiectului, elementelor de matrice și
parametrilor funcțiilor.
În plus față de concat(), wrapperii de șiruri de caractere oferă următorii membri. Rețineți că le
vom explora doar pe cele vitale. Rețineți, de asemenea, că, cu excepția String.fromCharCode(), va
trebui să înlocuim identificatorul String cu un literal de șir de caractere sau o expresie de șir de
caractere, de obicei doar numele unei variabile, al unui membru, al unui element sau al unui parametru
care conține un șir de caractere. Cu toate acestea, orice expresie care returnează un șir de caractere este
suficientă.
String.charAt()
String.charCodeAt()
String.concat()
String.fromCharCode()
String.indexOf()
String.lastIndexOf()
String.length
String.localeCompare()
String.match()
String.replace()
String.search()
String.slice() String.split()
String.substring()
String.substr()
String.toLocaleLowerCase()
29
CAPITOLUL 2 ■ CONVERSIA DE TIP

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ă.

Determinarea numărului de caractere


Pentru șirul de caractere "Batman", Firefox ar crea un înveliș ca următorul obiect literal. Amintiți-vă
din capitolul 1 că un obiect poate avea elemente la fel ca o matrice. Astfel, acest obiect conține
șase elemente numerotate de la 0 la 5.
{"0": "B", "1": "a", "2": "t", "3": "m", "4": "a", "5": "n"}
Având în vedere acest lucru, putem interoga numeric caracterele din "Batman" cu ajutorul
operatorului []. Încercați să faceți acest lucru în Firebug, verificând munca dvs. cu figura 2-5:
"Batman"[3];
// "m"
"Batman"[0];
// "B"

Figura 2-5. Interogarea elementelor dintr-un obiect wrapper

Î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

"Mr. Incredible, Elastigirl, Violet, Dash, Jack-Jack".lungime;


// 51
"Underminer, Sindromul, Călătorie cu bombă".lungime;
// 33
La fel cum puteți interoga elementul final dintr-o matrice prin scăderea lui 1 din membrul length,
puteți interoga caracterul final, adică elementul final, dintr-un șir de caractere în același mod. În mod
similar, dacă se scade 2 din lungime se obține penultimul caracter, dacă se scade 3 se obține penultimul
caracter și așa mai departe:
var parrFamily = "Mr. Incredible, Elastigirl, Violet, Dash, Jack-Jack";
parrFamily[parrFamily.length - 1];
// "k" parrFamily[parrFamily.length
- 15];
// "D"
Interogarea elementelor dintr-un obiect wrapper cu ajutorul operatorului [] este o caracteristică
proprie a Firefox care este utilă pentru a înțelege modul în care sunt reprezentate șirurile de caractere cu
ajutorul obiectelor wrapper. Cu toate acestea, ECMAScript nu cere interpreților JavaScript să o
suporte. Așadar, Internet Explorer și alte browsere nu o fac. Prin urmare, cel mai bine este să
interogați caracterele în mod standard - trecând indexul elementului, cu alte cuvinte, numărul
acestuia, la charAt(). Deși nu este la fel de convenabil, acest lucru funcționează între browsere.
Încercați următorul exemplu în Firebug, verificând munca dvs. cu figura 2-6:
var parrFamily = "Mr. Incredible, Elastigirl, Violet, Dash, Jack-Jack";
parrFamily.charAt(7);
// "r"
parrFamily.charAt(parrFamily.length - 1);
// "k"

Figura 2-6. Interogarea elementelor în mod standard este mai puțin convenabilă, dar funcționează între browsere.
31
CAPITOLUL 2 ■ CONVERSIA DE TIP

Decodarea sau codificarea caracterelor


Pentru caracterele care nu sunt de la tastatură, este de obicei mai simplu să se lucreze cu codificarea
Unicode decât cu caracterul. De exemplu, Dr. Otto Günther Octavius este identitatea secretă a unuia
dintre dușmanii lui Spider-Man, Doctor Octopus. În loc să încercați să tastați ü în Günther, treceți
codificarea Unicode (252) a acestuia la String.fromCharCode(), astfel în Firebug, verificând munca dvs.
cu ajutorul figurii 2-7:
var id = "Dr. Otto G" + String.fromCharCode(252) + "nther Octavius"; id;
// "Dr. Otto Günther Octavius"

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

Figura 2-9. Conversia în majuscule sau minuscule

Turca are versiuni cu puncte și fără puncte ale lui i:

• İ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()
// "İ"

■ Rețineți că toLocaleLowerCase() și toLocaleUpperCase() convertesc majusculele în funcție de setările


sistemului de operare. Ar trebui să modificați aceste setări în turcă pentru ca exemplul anterior să
funcționeze. Sau credeți-mă pe cuvânt!

34
www.allitebooks.com
CAPITOLUL 2 ■ CONVERSIA DE TIP

Localizarea unui subșir


Uneori, veți dori să căutați un șir de caractere pentru un șir mai mic, denumit subșir. De exemplu, "man"
este un subșir din "Batman" și "Catwoman". O modalitate de a face acest lucru este cu indexOf(), care
funcționează cu doi parametri:

• Subșirul care trebuie căutat

• Un index opțional pentru locul de unde se începe căutarea


În cazul în care se găsește subșirul, indexOf() returnează indexul primului caracter care
corespunde. În caz contrar, returnează -1 pentru a transmite eșecul. Așadar, în Firebug, să determinăm
unde începe substringa "Ghost" într-un literal care conține câțiva dintre dușmanii lui Iron Man:
"Iron Monger, Titanium Man, Madame Masque, G h o s t , Mandarin".indexOf("Ghost");
// 42
Încercați să faceți acest lucru indirect, printr-o variabilă care conține literalul. Introduceți "Mandarin"
și apoi "Green Goblin", care este responsabilitatea lui Spider-Man. Așadar, așa cum arată figura 2-10,
JavaScript confirmă acest lucru returnând -1:
var villains = "Iron Monger, Titanium Man, Madame Masque, Ghost, Mandarin";
villains.indexOf("Mandarin");
// 49
villains.indexOf("Green Goblin");
-1

Figura 2-10. indexOf() returnează -1 pentru a transmite un eșec.

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

Figura 2-11. Localizarea celei de-a doua apariții a cuvântului "Man"

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

Tăierea unui subșir


Uneori este posibil să doriți să tăiați o felie dintr-un șir. Pentru a face acest lucru, treceți doi parametri la
slice():

• Indicele primului caracter din felie


• Indicele primului caracter după slice
Astfel, pentru a tăia n caractere începând cu indexul i, se trece i ca prim parametru și i + n ca al
doilea parametru. Rețineți că indicii de caractere încep de la 0.
"Superman, Batman, Spider-Man, Iron Man".slice(18, 24);
// "Spider"
36
CAPITOLUL 2 ■ CONVERSIA DE TIP

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"

Figura 2-12. Decuparea unei subșiruri cu slice()

Înlocuirea unui subșir


Dacă doriți să înlocuiți o parte a unui șir de caractere, apelați metoda replace(), care funcționează cu doi
parametri:

• Ș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"

Figura 2-13. Înlocuirea unei subșiruri cu replace()

Acum, să creăm o funcție de clasificare a titlurilor numită titleCase(), pe care să o trecem ca al


doilea parametru, astfel încât să putem înlocui numele fiecărui erou cu versiunea sa cu majuscule.
JavaScript va transmite titleCase() textul potrivit, la care ne putem referi ca m în cadrul blocului de
funcții. Acolo vom înlănțui invocările lui slice() și

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-14. Al doilea parametru pentru replace() poate fi o funcție.

Împărțirea unui șir de caractere într-o matrice de șiruri mai mici


Dacă doriți să împărțiți un șir de caractere în șiruri mai mici, transmiteți metodei split() un separator.
Aceasta va împărți apoi șirul în șiruri mai mici, denumite subșiruri, în funcție de locul în care apare
separatorul. Aceste subșiruri nu includ separatorul și sunt returnate într-o matrice de către split().
Curățați de două ori Firebug și haideți să împărțim o listă de arhicunoscuți ai Omului-Păianjen în
raport cu o virgulă urmată de un spațiu. Vom trece ", " la split() în felul următor, verificând munca
noastră cu ajutorul figurii 2-15:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin, Sandman"; villains.split(",
" ");
// ["Green Goblin", " Doctor Octopus", "Venom", " Hobgoblin", "Sandman"]
39
CAPITOLUL 2 ■ CONVERSIA DE TIP

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

Figura 2-16. Transmiterea unui obiect RegExp la split()

Vă amintiți din Capitolul 1 cum ați interogat matricea returnată de split()?


Uh-huh. Trecând un index la operatorul []. Astfel, pentru a returna al patrulea element,
"Hobgoblin", vom trece [] indicele 3, deoarece JavaScript numerotează elementele începând cu 0:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin, and Sandman";
villains.split(/, ( ?:and ) ?/g)[3];
// "Hobgoblin"
A funcționat bine. Dar ce s-a întâmplat cu matricea? Nu este în răufăcători, după cum arată figura 2-17:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin, and Sandman";
villains.split(/, (?:and )?/g)[3];
// Răufăcătorii
"Hobgoblin";
// "Green Goblin, Doctor Octopus, Venom, Hobgoblin și Sandman"

41
CAPITOLUL 2 ■ CONVERSIA DE TIP

Figura 2-17. Unde a dispărut matricea de subșiruri?

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

Căutarea cu expresii regulate


În timp ce indexOf() returnează indicele primei corespondențe dintr-un șir de caractere, search()
returnează indicele primei corespondențe dintr-un obiect RegExp. Așadar, următoarele exemple sunt
echivalente:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin și Sandman";
villains.indexOf("Goblin");
// 6
răufăcători.search(/Goblin
/);
// 6
În cazul în care doriți să obțineți toate corespondențele și nu doar pe prima, treceți în schimb
RegExp la match(). JavaScript va returna apoi o matrice de subșiruri corespunzătoare. Haideți să
găsim orice apariție a lui goblin, indiferent de caz în Firebug, verificând munca noastră cu ajutorul
figurii 2-19:
var villains = "Green Goblin, Doctor Octopus, Venom, Hobgoblin și Sandman";
villains.match(/[Gg]oblin/g);
// ["Goblin", "goblin"]
Vom reveni la search() și match() în capitolul 5, unde veți învăța să scrieți modele RegExp mai
interesante.

Figura 2-19. Transmiterea unui obiect RegExp către match()

Crearea explicită a înfășurătoarelor


În plus față de membrii furnizați de funcțiile de construcție String(), Number() sau Boolean(),
obiectele wrapper primesc și următorii membri de la Object(). Singurul pe care îl vom explora acum
este valueOf(); celelalte sunt tratate în capitolul 5.
constructor
hasOwnProperty()
isPrototypeOf()
43
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.

Conversia unei valori în alt tip


Elastigirl, soția lui Mr. Incredible, Helen Parr, poate să-și remodeleze orice parte a corpului pentru a fi
cât mai mare de 30 de metri sau cât mai mică de 1 milimetru. De exemplu, în filmul de animație Pixar
"The Incredibles" din 2004, Elastigirl a salvat ziua în mod repetat, remodelându-și corpul într-o
parașută, o plută de cauciuc și așa mai departe. La fel ca Elastigirl, și valorile JavaScript sunt
schimbătoare de forme. Ele pot salva ziua prin schimbarea în alt tip de valoare, de exemplu, de la un
număr la un șir de caractere. Iată cum.
44

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

Figura 2-21. Conversia valorilor în tipul boolean

■ 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

Conversia unei valori într-un număr


Acum să încercăm să convertim câteva valori într-un număr, trecându-le la Number(). Curățați de două
ori Firebug, apoi încercați să convertiți nimic, nedefinit sau nul, într-un număr:
Număr(nedefinit);
// NaN
Număr(nul);
// 0
În timp ce atât undefined, cât și null se convertesc în același boolean (false), acestea se
convertesc în numere diferite: undefined în NaN și null în 0. Rețineți că NaN ("not a number") este un
număr literal special pe care JavaScript îl returnează pentru erori matematice, cum ar fi împărțirea la
0, sau pentru conversiile la tipul de număr care eșuează.
46
CAPITOLUL 2 ■ CONVERSIA DE TIP

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.

Conversia șirurilor de caractere în numere este destul de simplă. Șirurile de caractere


asemănătoare numerelor, cum ar fi "4" sau "3,33", se convertesc într-un număr echivalent (4 și 3,33).
Șirul gol "" se convertește în 0. Orice altceva se convertește în NaN. Să încercăm să convertim câteva
șiruri de caractere într-un număr în Firebug, verificând munca noastră cu ajutorul figurii 2-23:
Număr("4");
// 4
Număr("");
// 0
Număr("Mr. Incredible");
// NaN
47
CAPITOLUL 2 ■ CONVERSIA DE TIP

Figura 2-23. Cele mai multe șiruri de caractere se convertesc în NaN.

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.

Tabelul 2-1 afișează dintr-o privire conversiile de numere pe care le-am

explorat. Tabelul 2-1. Exemple care prezintă spectrul de conversii în tipul de

număr Valoarea inițialăNumăr ()

nedefinit NaN

null 0

"" 0

"30px" NaN

"4" 4

"3.33" 3.33

adevărat 1

fals 0

["Green Goblin", "Doctor Octopus", " Sandman"] NaN

{eroul: "Batman", dușmanul: "Joker"} NaN

String.fromCharCode NaN
49
CAPITOLUL 2 ■ CONVERSIA DE TIP

Conversia unei valori într-un șir de caractere


În capitolul 1, ați învățat că operatorul [] convertește un număr într-un șir de caractere. Așadar,
următoarele interogări returnează amândouă membrul numit "3":
"Mezmerella"[2];
// " z"
"Mezmerella"["2"];
// "z"
Deși JavaScript convertește frecvent numere în șiruri de caractere în spatele scenei, ocazional va
trebui să convertească valori de alte tipuri în șiruri de caractere. Nu este de mirare că trebuie să facă
acest lucru în cazul valorilor undefined, null sau boolean:
String(nedefinit);
// "undefined"
String(null);
// "null"
String(true);
// "true"
String(false);
// "false"
Pe de altă parte, conversia valorilor de tip obiect sau a subtipurilor de tablouri și funcții în șiruri
de caractere nu este simplă sau obișnuită. Pentru a face acest lucru, JavaScript apelează metoda
toString() a valorii. Următoarele conversii de la array la string sunt echivalente:
String(["Green Goblin", " Doctor Octopus", "Sandman"]);
// "Green Goblin,Doctor Octopus,Sandman"
["Green Goblin", " Doctor Octopus", "Sandman"].toString();
// "Green Goblin,Doctor Octopus,Sandman"
La fel și următoarele conversii de la obiect la șir de caractere, după cum arată figura 2-25.
Observați că obiectul cu minuscule indică tipul de valoare, iar Object cu majuscule indică clasa, adică
identificatorul pentru constructorul Object(). Figura 2-25 afișează rezultatele:
({"Bob Parr": " Mr. Incredible", "Helen Parr": "Elastigirl"}).toString();
// "[obiect Obiect]"
String({"Bob Parr": " Mr. Incredible", "Helen Parr": "Elastigirl"});
// "[obiect Obiect]"
Nu vă dedicați prea multe celule cerebrale pentru a converti obiecte, matrice sau funcții în șiruri
de caractere. Nu este important de știut.
50
CAPITOLUL 2 ■ CONVERSIA DE TIP

Figura 2-25. Conversia unei matrice și a unui obiect într-un șir de caractere

Metode de conversie a unui număr într-un șir de caractere


Fiul de 10 ani al domnului Incredibil, Dashiell "Dash" Robert Parr, este un vitezoman ca și Flash.
Probabil că poate alerga cu viteza luminii: 299.792.458 de metri pe secundă. Este posibil ca numerele
din JavaScript să nu conțină virgule, așa că numărul literal pentru acest lucru ar fi 299792458. Este
destul de urât.
Nu vă faceți griji. Înfășurătoarele de numere oferă următoarele trei metode pentru a converti un
număr umflat, cum ar fi 299792458, într-un șir succint.
Number.toExponential()
Number.toFixed()
Number.toPrecision()
Curățați de două ori Firebug și să apelăm fiecare dintre acestea pe rând pe 299792458. Mai întâi,
toExponential() convertește un număr într-un șir exponențial. Opțional, puteți indica numărul de
zecimale, trecând un număr între 0 și 20. Încercați să treceți 2 și să omiteți parametrul, verificând
munca dvs. cu figura 2-26:
(299792458).toExponential(2);
// "3.00e+8"
(299792458).toExponential();
// "2.99792458e+8"
51
CAPITOLUL 2 ■ CONVERSIA DE TIP

Figura 2-26. Conversia unui număr într-un șir de caractere cu toExponential()

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"

Figura 2-27. Conversia unui număr într-un șir de caractere cu toFixed()


52
CAPITOLUL 2 ■ CONVERSIA DE TIP

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"

Figura 2-28. Conversia unui număr într-un șir de caractere cu toPrecision()

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ă.

Amânarea învățării sintaxei RegExp


Chiar dacă voi aborda mai pe larg obiectele RegExp în capitolul 5, dacă sunteți nou atât în JavaScript,
cât și în programare, vă sugerez să treceți pur și simplu parametri de tip șir de caractere la cele patru
metode care lucrează cu obiecte RegExp. replace() și split() lucrează fie cu un șir de caractere, fie cu
un parametru RegExp. Așadar, un șir de caractere va merge ca atare. match() și search() funcționează
numai cu un parametru RegExp, dar JavaScript convertește implicit un parametru de tip șir de caractere
într-un obiect RegExp. Așadar, așa cum un șir de caractere este convertit într-un obiect wrapper prin
trecerea lui la new și String(), acesta poate fi convertit într-un obiect RegExp prin trecerea lui la new și
RegExp(). În măsura în care JavaScript face aceasta din urmă la fel de liniștit ca și prima, acest lucru
înseamnă că un începător poate amâna învățarea sintaxei RegExp până după ce învață sintaxa
JavaScript. Vă recomand să faceți acest lucru.

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.

Introducerea precedenței și asociativității operatorilor


Am alergat mai mult de 160 de kilometri pe săptămână timp de 14 ani. Pentru a consuma suficienți
carbohidrați zilnic, am devenit nu atât un gurmand, cât un gurmand. De luni până vineri alerg seara,
după serviciu. Așa că atunci când încep să pregătesc cina, este destul de târziu. Pentru a economisi
ceva timp, de obicei merg pe drojdie de sodiu în loc de drojdie.
Una dintre pâinea mea preferată de seara târziu este cea cu sifon, care se leagă cu sifon, cremă de
tartru și iaurt de vanilie Organic Valley, care se poate bea ca și kefirul, dar are gust de iaurt:

• 2 căni de făină de patiserie integrală organică Bob's Red Mill


• 1/3 cană de migdale albite, proaspăt măcinate în făină fină

• 1 linguriță de scorțișoară Saigon

• 2 lingurițe de coajă de lămâie tocată mărunt

• 1/4 linguriță de sare de mare

• 1 linguriță de sifon

• 1 linguriță de cremă de tartru

• 1 ceașcă de iaurt de vanilie organic Valley

• 1 ou

• 1 1/4 cești de afine proaspete sălbatice


57
CAPITOLUL 3 ■ OPERATORI

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:

1. Se macină migdalele până se obține o făină foarte fină.


2. Coaja de lămâie.
3. Se cerne făina, migdalele, saigonCanela, lămâia și sarea de mare.

4. Bateți oul, iaurtul de vanilie și tartrul.

5. Se bate cu sifon pentru a atinge reacția alcalină și acidă.

6. Se amestecă lichidul care acum bolborosește cu ingredientele uscate.

7. Încorporați afinele sălbatice cu mâna.


8. Se frământă aluatul pentru a forma fire de gluten.
Okeydokey. Dar poate JavaScript să urmeze cu precizie o rețetă? Adică, cum poate evalua o expresie
în mod asincron?
Da, poți să pariezi pe ferigă.
Operatorii au câteva trăsături care determină ordinea de funcționare. Precedența este o valoare
între 1 și 15 care indică prioritatea unui operator. 1 reprezintă o prioritate foarte mică, iar 15 reprezintă o
prioritate foarte mare. Așadar, dacă o expresie conține succesiv operatori cu o prioritate de 2, 14, 9 și 3,
JavaScript efectuează mai întâi operația 14, urmată de 9, apoi de 3 și, în final, de 2.
Cealaltă trăsătură, asociativitatea, îi spune lui JavaScript ce trebuie să facă atunci când operatorii
consecutivi au aceeași prioritate. Aici există doar două opțiuni. Asociativitatea R înseamnă că trebuie să îl
faceți mai întâi pe cel din dreapta. În schimb, asociativitatea L înseamnă că trebuie să fie folosit mai întâi
cel din stânga. Astfel, dacă o expresie conține în mod secvențial operatori cu prioritățile 15, 15, 3 și 3 și
cu asociativitatea L, L, R și R, JavaScript îl face pe primul 15, apoi pe al doilea 15, apoi pe al doilea 3,
apoi pe primul 3.
Având în vedere acest lucru, să creăm câțiva operatori fictivi cu care JavaScript să lucreze:

• G pentru precedența grind-14, R asociativitate

• Z pentru precedența zest-14, R asociativitate

• S pentru precedență sift-11, L asociativitate

• W pentru precedență whisk-10, asociativitate R

• F pentru precedența fold-3, L asociativitate

• K pentru precedența knead-3, R asociativitate


Astfel, putem exprima acum aluatul în JavaScript cu următoarea expresie:
var aluat = K făină S G migdale S saigonCinnamon S Z lămâie S sare de mare W sodă W tartru W iaurt de vanilie
W ou F afine sălbatice;
58
CAPITOLUL 3 ■ OPERATORI

Să deslușim acest lucru în etape:

1. În regulă, atât G, cât și Z au 14 priorități. Deci, JavaScript evaluează mai întâi


migdalele G sau lămâia Z? Dacă doi operatori au prioritate egală, dar nu sunt
adiacenți, JavaScript lucrează de la stânga la dreapta. Prin urmare, migdalele sunt
măcinate, iar apoi lămâia este curățată de coajă.

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.

6. Nu chiar. = este și el un operator. Dar are o prioritate foarte mică, de 2. Doar


operatorul ,, pe care îl vom analiza puțin mai târziu, are o prioritate de 1. Așadar,
JavaScript efectuează operațiile G, Z, S, W, F și K înainte de a atribui pâinea la
variabila aluat cu ajutorul operatorului =.
Operatorul =, care are prioritate scăzută și asociativitate R, vă permite să atribuiți o expresie la două
sau mai multe variabile. Așadar, dacă am dori să facem trei pâini cu afine sălbatică în loc de una, am
face acest lucru prin înlănțuirea operatorilor =:
var loaf1, loaf2, loaf3;
pâine1 = pâine2 = pâine3 = K făină S migdale S saigonCanela S lămâie S mareSare W sodă W tartru W
turnabilăVanilieYogurt W ou F afine sălbatice;
JavaScript ar efectua mai întâi operațiile cu prioritate mai mare G, Z, S, W, F și K. În măsura în care =
are asociativitatea R, JavaScript ar atribui mai întâi swope wild blueberry swope la loaf3, apoi la loaf2 și,
în final, la loaf1.
Această secțiune v-a oferit o idee despre cum funcționează în principiu precedența operatorilor,
așa că haideți să analizăm operatorii JavaScript, precedența lor și modul de utilizare.
59
CAPITOLUL 3 ■ OPERATORI

Utilizarea operatorilor JavaScript


Pe măsură ce explorăm operatorii JavaScript în acest capitol, este posibil să doriți să consultați tabelul 3-1,
care enumeră valorile de precedență și asociativitate. Deși probabil că vă gândiți că nu vă veți aminti
niciodată precedența și asociativitatea pentru toți acești operatori, este de fapt foarte simplu:

• 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ă.

• Operatorul ternar ?: are asociativitatea R și precedența 3.


Reamintirea tipurilor de valori pentru operanzi va necesita timp. Dar peste un an, dacă nu până la
sfârșitul acestei cărți, acestea vor fi la fel de ușor de reținut ca asociativitatea și precedența.

Tabelul 3-1. Precedența și asociativitatea pentru operatorii JavaScript esențiali

Operator Precedență Asociativitat Operator Precedență Asociativitat


e e
. 15 L <= 10 L
[] 15 L instanceof 10 L
() 15 L în 10 L
nou 15 R == 9 L
++ 14 R != 9 L
-- 14 R === 9 L
! 14 R !== 9 L
șterge 14 R && 5 L
typeof 14 R || 4 L
void 14 R ?: 3 R
* 13 L = 2 R
/ 13 L *= 2 R
% 13 L /= 2 R
+ 12 L %= 2 R
- 12 L += 2 R
> 10 L -= 2 R
>= 10 L , 1 L
< 10 L
60
CAPITOLUL 3 ■ OPERATORI

■ 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.

Combinarea operațiilor matematice și de atribuire


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, am explorat
operatorii + (adunare), - (scădere), * (înmulțire) și / (împărțire), observând că + adaugă numere, dar
lipește șiruri de caractere. În plus, am explorat modul de salvare a unei valori într-o variabilă cu
ajutorul operatorului = (atribuire).
În cazul în care operandul din stânga la +, -, * sau / este o variabilă, un membru, un element sau un
parametru, puteți înlocui operatorul = și operatorul +, -, * sau / cu un operator de prescurtare +=, -=,
*= sau /=. Aceștia efectuează operațiile matematice și de atribuire dintr-o singură lovitură. În Firebug,
haideți să creăm următorul obiect de aluat, astfel încât să avem câteva valori cu care să explorăm +=, -=,
*= și /=.
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ă"]
};
Să zicem că vreau să triplez rețeta. Pentru a face acest lucru, am putea trece fiecare element și 3 la
operatorul *= astfel
în Firebug. Apoi, interogați noile valori ale câtorva elemente, verificând munca dvs. cu figura 3-1:
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.pastryFlour[0] *= 3;
aluat.migdaleFăină[0] *= 3;
dough.saigonCinnamon[0] *= 3;
aluat.mincedLemonZest[0] *= 3;
dough.seaSalt[0] *= 3;
dough.soda[0] *= 3;
aluat.tartar[0] *= 3;

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

Figura 3-1. Dublarea elementelor cu *=

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

Figura 3-2. Triplarea elementelor în mod dificil cu = și *

■ 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

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.pourableVanillaYogurt[0] += "1";
dough.pourableVanillaYogurt[0];
// "11"

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"
}}

Creșterea sau descreșterea valorilor


În cazul în care adăugați sau scădeți 1 dintr-o valoare cu += sau -=, puteți face acest lucru și mai
simplu cu operatorii unari ++ increment și -- decrement, care o convertesc în număr dacă este necesar.
Rețineți că, deși += poate efectua adunarea sau concatenarea, ++ efectuează întotdeauna adunarea.
Deci, în Firebug, să dublăm saigonCinnamon cu ++ și să reducem la jumătate mincedLemonZest cu --,
verificând munca noastră cu Figura 3-6.
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.saigonCinnamon[0] ++; aluat.lămâie
tocată măruntZest[0] --;
66
CAPITOLUL 3 ■ OPERATORI

aluat.saigonCinnamon[0];
// 2
dough.mincedLemonZest[0];
// 1

Figura 3-6. ++ este o prescurtare pentru += 1, iar -- este o prescurtare pentru -= 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

Figura 3-7. Valoarea de returnare pentru ++ diferă în poziția prefixului și postfixului.

Valorile de returnare diferite ale ++ și -- în pozițiile prefix și postfix oferă o flexibilitate


suplimentară în limitarea numărului de ture pe care JavaScript le face într-o buclă for, while sau do while.
Acestea sunt tratate în capitolul 4.

Testarea pentru egalitate


Scones sunt deosebit de rapid de făcut - poate 20 de minute de la amestecarea aluatului până la punerea
scones pe grătarul de răcire. Scones cu cremă de alune sunt printre preferatele mele. Le fac fie cu frișcă
groasă Organic Valley, fie cu frișcă groasă sau cu jumătate și jumătate. Acestea sunt delicios de dulci și
cremoase datorită faptului că Organic Valley își paște vacile în loc să le hrănească cu porumb.
Rețineți că, în cazul biscuiților cu cremă, bulele de leuștean provin din reacția sodei cu cremă de tartru.
Adică, frișca de frișcă sau jumatea nu conțin acid lactic. Rețineți că raportul dintre lingurița de sifon și
ceștile de făină este de 1:4 pentru scones, față de 1:2 pentru pâine; nu dorim atât de multă ridicare.
Rețineți de asemenea că
68
CAPITOLUL 3 ■ OPERATORI

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 cerne făina de patiserie, făina de alune, zahărul și sarea de mare.

• 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.

• Se face o fântână în centrul făinii, se toarnă ingredientele umede și coacăzele


și se amestecă rapid cu o spatulă de cauciuc pentru a forma un aluat moale,
ușor umed.

• Întoarceți aluatul pe o suprafață de lucru bine tapetată cu făină și


întindeți-l într-o formă rotundă cu diametrul de 9 inci (1/2 inci grosime).

• Se taie în 8 felii cu un cuțit ascuțit și presărat cu făină. Se taie și se aruncă centrul


cu o freză de biscuiți de 3 inci.

• Coaceți bucățile ușor separate pe o foaie tapetată cu pergament timp de 12 minute la 425°F.

• Se lasă să se răcească pe un grătar de sârmă timp de zece minute.


Rețineți că rularea aluatului pentru scones sau alte produse de patiserie poate fi dificilă. Așa că,
dacă ești un aluatar în devenire, îți recomand să cumperi o planșetă de patiserie din marmură. În
măsura în care marmura rămâne mai rece decât temperatura camerei, este mai ușor să rulezi aluatul pe
ea.
Oricum, să spunem că aș vrea să compar presupunerea mea despre cât de mult ingredient să adaug
în aluat cu ceea ce cere rețeta. Aș putea face acest lucru prin intermediul operatorului de identitate ===.
=== returnează adevărat dacă operanzii săi se evaluează la valori identice și fals dacă nu. Acest verdict
boolean derivă din următorul protocol:
• În cazul în care valorile sunt de tipuri diferite, se returnează false.

• Dacă ambele valori sunt de tip nedefinit, se returnează true.


• Dacă ambele valori sunt de tip null, se returnează true.

• Î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.

• În cazul în care ambele valori sunt de tip șir de caractere și au aceeași


secvență și același număr de caractere, se returnează true. În caz contrar, se
returnează false.
• Dacă ambele valori sunt de tip boolean, se returnează true dacă ambele sunt false sau dacă ambele
sunt
adevărat. În caz contrar, se returnează false.

69
CAPITOLUL 3 ■ OPERATORI

• Dacă ambele adrese de memorie se referă la aceeași locație, se returnează


true. În caz contrar, se returnează false.
Ți-a trecut acest ultim pas peste cap? Nu vă faceți griji, în capitolul 5 vom explora compararea
adreselor de memorie, care, împreună cu indicatoarele, sunt denumite în general referințe. Deocamdată,
trebuie doar să știi că undefined, null, numerele, șirurile de caractere și booleenii sunt comparate prin
valoare, în timp ce obiectele, array-urile și funcțiile sunt comparate prin adresa de memorie, adică prin
referință (ceea ce implică faptul că cele două seturi sunt stocate în moduri diferite, despre care vom
vorbi mai mult în capitolul 5).
Rețineți că === nu face conversia tipului de date, dar predecesorul său, operatorul de egalitate ==,
face acest lucru. În măsura în care == vă poate spune dacă doar o expresie nu este complet diferită de
alta, programatorii JavaScript pricepuți nu sunt de acord cu utilizarea sa. Așadar, din moment ce
sunteți o tabula rasa, nu vă voi învăța aici obiceiuri proaste.
Destul cu teoria lui ===; în Firebug, încercați următorul exemplu, verificând munca dvs. cu Figura
3-8:
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;
// fals
aluat.coacăze[0] === aluat.făină de alune[0];
// adevărat
dough.hazelnutFlour[0] * 5 === dough.pastryFlour[0];
// adevărat
aluat.sifon[0] / aluat.tartru[0] === 1;
// fals

Figura 3-8. Verificarea cantității de cremă de tartru și sifon cu ===


70
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

Figura 3-10. !== face un not logic pe verdictul protocolului ===.

Compararea obiectelor, a tablourilor și a funcțiilor


Până acum am comparat șiruri de caractere și numere cu ===. Astfel, JavaScript nu a ajuns niciodată la
etapa finală în care se compară adrese de memorie și nu valori. Referindu-ne la protocolul ===, nu
există nicio etapă în care JavaScript compară plictisitor membrii obiectelor, elementele de matrice sau
corpurile funcțiilor. Nu este o eroare
72
CAPITOLUL 3 ■ OPERATORI

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

Figura 3-11. Matrici separate, dar identice, nu sunt egale.

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

tartru: [1, "tsp"],


cremă de frișcă: [1, "ceașcă"],
coacăze: [1/3, "ceașcă"]
};
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ă"]
};
// fals
aluat === aluat;
// adevărat
dough.pastryFlour === [1 + 2/3, "cup"];
// fals
dough.pastryFlour === dough.pastryFlour;
// adevărat

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.

Determinarea dacă un număr sau un șir de caractere este mai mare


decât altul
Dacă ați strecura cea mai mare parte a zerului apos dintr-o cutie de 1 litru de iaurt din lapte integral, care
are 4 la sută smântână, ați obține 1 2/3 cești de iaurt grecesc delicios, yiaourti, care are 10 la sută
smântână. Mmmh. Mai mult, dacă ați strecura tot zerul apos, ați obține 1 1/3 cești de b r â n z ă de iaurt, o
brânză cremă mai sănătoasă. Pentru a face acest lucru, pur și simplu puneți o sită căptușită cu opt straturi
de cașcaval deasupra unui bol în frigider, aruncați iaurtul și lăsați zerul să se scurgă timp de 6 ore pentru a
face yiaourti sau 12 ore pentru a face brânză de iaurt. Cu toate acestea, fabricile de iaurt grecesc și de
brânză de iaurt sunt ieftine; eu l-am cumpărat pe al meu cu 21 de dolari de pe www.kingarthurflour.com,
dar Target are unul la 16 dolari.
În măsura în care brânza de iaurt păstrează acidul lactic din iaurt, se poate leșina aluatul cu ea în
loc de smântână. Una dintre prăjiturile mele preferate cu mere procedează astfel. În plus, glazura este
făcută cu brânză de iaurt bătută în loc de cremă de brânză. Rețeta poate fi reprezentată prin înglobarea
obiectelor aluat și glazură în următorul obiect tort. Rețineți că trebuie să procedați astfel:

• 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 mărunțesc 1 2/3 căni de mere Granny Smith acrișoare.

• Se cerne făina de patiserie integrală bio Red Mill cu nucșoara și scorțișoara.

• Se bat 2 ouă, cu 2/3 cană de brânză de iaurt și 1/3 cană de sirop de arțar pur.

• Se dizolvă tartrul și sifonul în lichid.

• Se amestecă imediat lichidul cu ingredientele uscate cernute.

• Încorporați Granny Smith mărunțit și nucile pecan tocate.

• Coaceți timp de 40 de minute la 350ºF.

• 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.

• Așteptați ca tortul să se răcească înainte de a-l acoperi cu glazură.


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"],

75
CAPITOLUL 3 ■ OPERATORI

PureMapleSyrup: [1 + 1/3, "tbs"],


GroundPecans: [2, "tsp"]
}
};
Dacă vrem să știm dacă e mai multă făină de patiserie sau Granny Smiths mărunțită în
aluat? Nici ===, nici !== nu v-ar fi de ajutor. În schimb, vom compara aceste ingrediente cu operatorul
> greater than, care, la fel ca === și !==, returnează un verdict boolean - adevărat dacă primul operand
este mai mare decât al doilea operand, iar dacă nu, fals. Încercați să comparați unii membri cu
operatorul >, verificând munca dvs. cu ajutorul figurii 3-13:
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, "linguriță"],
nuci de pădure măcinate: [2, "tsp"]
}
};
tort.aluat.organicPastryFlour[0] > tort.aluat.shreddedGrannySmith[0];
// fals
cake.dough.choppedPecans[0] > cake.dough.pureMapleSyrup[0];
// adevărat
tort.aluat.freshlyGroundNutmeg[0] > tort.aluat.saigonCinnamon[0];
// fals
tort.icing.yogurtCheese[0] > tort.aluat.yogurtCheese[0];
// fals
76
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

saigonCinnamon: [1/2, "tsp"],


soda: [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.dough.organicPastryFlour[0] < cake.dough.shreddedGrannySmith[0];
// adevărat
cake.dough.choppedPecans[0] < cake.dough.pureMapleSyrup[0];
// fals
tort.aluat.freshlyGroundNutmeg[0] < tort.aluat.saigonCinnamon[0];
// adevărat
tort.icing.yogurtCheese[0] < tort.dough.yogurtCheese[0];
// fals

Figura 3-14. Determinarea faptului dacă un număr este mai mic decât altul cu ajutorul operatorului >

78
CAPITOLUL 3 ■ OPERATORI

Mai mare sau egal cu, mai mic sau egal cu


OK, în ambele probe > și <, compararea brânzei de iaurt din aluat și din glazură a returnat fals,
deoarece acestea sunt egale - 2/3 de cană. Așadar, dacă am vrea să știm dacă un membru este mai mare
sau egal cu (sau mai mic sau egal cu) un alt membru, > și < nu ne-ar fi de niciun ajutor.
Sau cel puțin așa s-ar părea: A spune "nu mai puțin de" este același lucru cu a spune "mai mare sau egal cu",
și
a spune "nu mai mare decât" este același lucru cu a spune "mai mic sau egal cu". Așadar, vom inversa
verdictul boolean al lui < cu ! pentru a face o operație "mai mare sau egal cu". Invers, dacă întoarcem
verdictul lui > cu ! vom face o operație "mai mică sau egală cu". Așadar, haideți să o facem în Firebug,
verificând munca noastră cu Figura 3-
15. Rețineți că > și < au o prioritate de 10, așa că trebuie să depășim prioritatea de 14 a operatorului ! prin
înfășurarea operatorului >
sau < expresie între paranteze:

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

Figura 3-15. Inversarea verdictului boolean al lui > sau < cu !

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

cake.dough.organicPastryFlour[0] <= cake.dough.shreddedGrannySmith[0];


// adevărat
cake.dough.choppedPecans[0] >= cake.dough.pureMapleSyrup[0];
// adevărat

Figura 3-16. Compararea ingredientelor cu >= și <=

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.

Crearea unor comparații mai complexe


Brown Cow, iaurtul meu organic preferat cu cremă, are o aromă de arțar care este doar iaurt îndulcit cu
sirop de arțar pur. Comparând etichetele de Brown Cow cu sirop de arțar și de Brown Cow simplu cu
cele de sirop de arțar pur, am calculat că există 1 lingură de sirop de arțar pur la fiecare ceașcă de
Brown Cow cu arțar. Așa că, dacă fac brioșe cu boboci și nu am Brown Cow cu arțar în frigider, voi
îndulci 1 1/2 cești de Brown Cow simplu sau Stonyfield cream-top cu 1 /12 lingurițe de sirop de arțar
pur. Rețineți că boysenberries au fost create de horticultorul Charles Boysen în 1923 din mai multe
soiuri de mure, zmeură și

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.

2. Se bat secvențial arțarBrownCow, tartrul și sifonul.


3. Se amestecă imediat cu ingredientele uscate cernute.
4. Încorporați boabele de afine și boabele de păstăi tocate.

5. Umpleți cupele de brioșe până la 2/3.

6. Coaceți timp de 20 de minute la 375°F. Totuși, acest timp va varia în funcție


de mărimea cupelor de brioșe.
7. Se lasă să se răcească pe un grătar de sârmă timp de 10 minute.
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ă"]
};
Bine, să spunem că am dori să facem o comparație mai complexă. Să spunem că trebuie să verificăm
dacă una dintre cele două comparații este validă. Sau că ambele sunt valide. Poate chiar că două din
cinci comparații sunt valide. Am putea face asta?
Da. Pe lângă faptul că putem spune "not" cu operatorul logic not !, putem spune "or" cu operatorul
logic or || și "and" cu operatorul logic and &&. Atât || cât și && returnează unul dintre cei doi operanzi
ai lor în raport cu booleanul pe care primul lor operand îl evaluează sau îl convertește. Dacă primul
operand se evaluează sau se convertește la true:

• || returnează primul său operand.


• &&& returnează al doilea operand.
Pe de altă parte, dacă primul operand se evaluează sau se convertește la fals:

• || returnează al doilea operand.


• &&& returnează primul său operand.
Rețineți că &&& și ||| convertesc doar primul lor operand într-un boolean pentru a determina ce
operand să returneze. Cu alte cuvinte, dacă ||| sau && aleg să returneze primul lor operand, valoarea
neconvertită este cea returnată.
82
CAPITOLUL 3 ■ OPERATORI

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.

• Valoarea de returnare pentru && se va converti la adevărat dacă primul și al doilea


operand se evaluează sau se convertește la adevărat. În caz contrar, valoarea de
returnare pentru || se va converti în fals.
Asta a fost simplu, dar de ce ai vrea să faci algebră booleană? În primul rând, operatorii pe care i-
am explorat pentru compararea expresiilor, ===, !==, ==, !=, >, <, <, >= și <=, toți returnează un
verdict boolean. Așadar, algebra booleană oferă o modalitate de a exprima comparații complexe. Pe
de altă parte, în măsura în care obiectele și funcțiile se convertesc în true, iar undefined se
convertește în false, algebra booleană este baza pentru testarea caracteristicilor DOM, pe care o vom
explora în detalii sângeroase în ultimele patru capitole ale acestei cărți.

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ă.

Spunând "și" cu &&


Deși || va returna adevărat dacă una dintre cele două comparații este adevărată, && va returna adevărat
numai dacă ambele comparații sunt adevărate. Pentru a exemplifica, încercați următoarele în Firebug,
verificând munca dvs. cu figura 3-18. Nu uitați să faceți clic pe Run (Executare) înainte de fiecare
comentariu - deci de patru ori în total.
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] > muffin.boysenberries[0] &&
muffin.oatFlour[0] === muffin.barleyFlour[0];
// fals
muffin.oatFlour[0] === m u f f i n . barleyFlour[0] &&
muffin.mapleBrownCow[0] > muffin.boysenberries[0];
// fals
muffin.boysenberries[0] > muffin.choppedPecans[0] &&
muffin.pastryFlour[0] > muffin.barleyFlour[0];
// adevărat
brioșă.boysenberries[0] < brioșă.choppedPecans[0] &&&
84
CAPITOLUL 3 ■ OPERATORI

muffin.pastryFlour[0] < muffin.barleyFlour[0];


// fals

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ă

Expresii && și înlănțuire


Așadar, || este destul de indulgent - doar unul dintre cele două sau mai multe || înlănțuite trebuie să
returneze adevărat pentru ca întreaga combinație să returneze adevărat. &&, pe de altă parte, este
foarte strict. Fiecare comparație dintr-un lanț de expresii && trebuie să fie validă pentru ca întregul
lanț să returneze adevărat. Astfel, după cum ilustrează figura 3-21, chiar dacă primele două comparații
sunt adevărate, deoarece a treia este falsă, lanțul de expresii && returnează fals. Așadar, întrebăm
JavaScript dacă toate cele cinci comparații sunt adevărate, nu dacă majoritatea dintre ele sunt
adevărate.
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];
// fals
87
CAPITOLUL 3 ■ OPERATORI

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.

Încatenarea expresiilor || și &&


Rețineți că && are o prioritate de 5, în timp ce || are o prioritate de 4. Prin urmare, în exemplul
următor, JavaScript evaluează cele două expresii && înainte de cele două expresii ||. Prin urmare, după
cum ilustrează figura 3-23, în momentul în care JavaScript efectuează operațiile ||, comparația a fost
simplificată la false || true || brioșă.pastryFlour < brioșă.barleyFlour. Așadar, nu este necesar ca
JavaScript să evalueze comparația finală <.
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.freshlyGroundNutmeg[0] < muffin.saigonCinnamon[0] &&
muffin.choppedPecans[0] <= m u f f i n . mapleBrownCow[0] ||
muffin.pastryFlour[0] < muffin.barleyFlour[0];
// adevărat
false || true || muffin.pastryFlour[0]< muffin.barleyFlour[0];
// adevărat
89
CAPITOLUL 3 ■ OPERATORI

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ă.

Returnarea condiționată a uneia dintre cele două valori


Ce se întâmplă dacă dorim să alegem condiționat o valoare de returnare, mai degrabă decât să o verificăm pur și
simplu? În acest caz, este vorba de ?:
operatorul condițional, singurul operator ternar din JavaScript, își câștigă existența. La fel ca || și &&, ?:: alege
un

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:

• || returnează al doilea operand.


• &&& returnează primul său operand.
• ?: returnează al treilea operand.
Deși toți cei trei operatori pot fi utilizați pentru a returna condiționat o valoare, operatorul
condițional ?: separă condiția booleană de posibilele valori de retur, ceea ce face ca acesta să fie
preferat pentru a face acest lucru. Iată un exemplu rapid:
var first = 30; var
second = 20;
var result = first > second ? "primul este mai mare" : "primul
este mai mic"; result;
//"primul este mai mare"
Aici, primul operand este evaluat la true, astfel încât ?: returnează al doilea operand. După cum
puteți vedea, valoarea de returnare nu este utilizată ca parte a testului condițional.
Dacă fac pâine de afine cu lapte bătut, aș adăuga 1/2 cană de lapte bătut ca în următorul obiect de
aluat. Rețineți că bulele de fermentare sunt create de sifonul care reacționează cu acidul citric din sucul
de portocale, acidul lactic din zară și acidul tartric din crema de tartru. Rețineți de asemenea că pentru
această rețetă îmi place să folosesc făina albă integrală de grâu tare organică Bob's Red Mill. Deși conține
la fel de mult tărâțe bogate în fibre și germeni bogați în vitamine ca și grâul roșu tare, grâul alb tare are
taninurile roșii amare. Prin urmare, făina integrală de grâu alb tare are o aromă mai blândă și mai dulce
decât cea roșie tare, ceea ce o face ideală pentru pâinea dulce cu sifon.
var aluat = {
hardWhiteWhiteWholeWheatFloare: [ 2,
" cup"], zahăr: [1/3, "cup"],
madagascarVanilla: [1, "tsp"],
orangeZest: [1, "tbs"],
sifon: [1, "tsp"],
tartru: [ 1, "linguriță"], suc
de portocale: [1/2,
"ceașcă"], lapte bătut: [1/2,
"ceașcă"], ou: [1],
merișoare: [2/3, "ceașcă"]
};
Dacă nu am lapte bătut și am chefir, aș adăuga 9/16 cană de chefir. Rețineți că sunt 16 linguri pe
cană, deci 9/16 cană de chefir înseamnă 1/2 cană plus 1 lingură.
var aluat = {
hardWhiteWhiteWholeWheatFloare: [ 2,
" cup"], zahăr: [1/3, "cup"],
madagascarVanilla: [1, "tsp"],
orangeZest: [1, "tbs"],
sifon: [1, "tsp"],
91
CAPITOLUL 3 ■ OPERATORI

tartru: [ 1, " linguriță"], suc


de portocale: [1/2,
"ceașcă"], chefir: [9/16,
"ceașcă"],
ou: [1],
merișoare: [2/3, "ceașcă"]
};
Dar dacă nu am lapte bătut sau chefir, dar am iaurt, aș adăuga 10/16 cești de iaurt, adică 1/2
ceașcă plus 2 lingurițe:
var aluat = {
hardWhiteWhiteWholeWheatFloare: [ 2,
" cup"], zahăr: [1/3, "cup"],
madagascarVanilla: [1, "tsp"],
orangeZest: [1, "tbs"],
sifon: [1, "tsp"],
tartru: [ 1, "linguriță"], suc
de portocale: [1/2,
"cană"], iaurt: [10/16,
"ceașcă"], ou: [1],
merișoare: [2/3, "ceașcă"]
};
Având în vedere acest lucru, să stabilim în mod condiționat cantitatea unui element
CulturedMilk[0] la 1/2, 9/16 sau 10/16, în funcție de faptul dacă există suficient lapte bătut, chefir sau
iaurt în frigider. Pentru a face acest lucru, vom înlănțui două expresii ?:, una ca valoare de retur a
celeilalte. După cum afișează figura 3-24, deoarece în frigider nu era suficient lapte bătut, dar era
suficient chefir, JavaScript a setat cantitatea la 9/16, care se evaluează la 0,5625 zecimale. Cu alte
cuvinte, prima expresie ?: returnează al doilea operand deoarece expresia condițională este adevărată;
dacă ar fi fost falsă, ar fi returnat a doua expresie ?:, care la rândul ei ar fi fost evaluată:
var fridge = {
lapte bătut: [1/3, "ceașcă"],
chefir: [1 + 1/2, "ceașcă"],
iaurt: [4, "ceașcă"],
};
var aluat = {
hardWhiteWhiteWholeWheatFloare: [ 2,
" cup"], zahăr: [1/3, "cup"],
madagascarVanilla: [1, "tsp"],
orangeZest: [1, "tbs"],
sifon: [1, "tsp"],
tartru: [ 1, " linguriță"], suc
de portocale: [1/2,
"ceașcă"], lapte de cultură:
[1/2, "ceașcă"], ou: [1],
merișoare: [2/3, "ceașcă"]
};
dough.culturedMilk[0] = fridge.buttermilk[0] < 1/2 && fridge.kefir[0] >= 9/16 ? 9/16 :
fridge.yogurt[0] >= 10/16 ? 10/16 :
alert("Fără pâine cu merișoare pentru
tine!"); dough.culturedMilk;
// [0.5625, "cup"]
92
CAPITOLUL 3 ■ OPERATORI

Figura 3-24. Alegerea condiționată a cantității de lapte de cultură cu ajutorul operatorului ?:.

Efectuarea a două expresii ca fiind una


Acum, dacă am dori să actualizăm și cantitatea de lapte bătut, kefir sau iaurt din frigider? În măsura în
care nu putem pune decât o singură expresie după simbolurile ? și : ale operatorului ?:, se pare că nu
avem noroc.
Nu este așa. JavaScript oferă operatorul , virgulă pentru situații ca aceasta. , funcționează cu doi
operanzi și pur și simplu evaluează ambii săi operanzi și returnează valoarea celui de-al doilea
operand. Așadar, dacă facem ca actualizarea frigiderului să fie primul operand al lui , iar valoarea pe
care dorim să o atribuim lui dough.culturedMilk[0] să fie al doilea operand, va funcționa perfect.
Încercați-o în Firebug, verificând munca dvs. cu Figura 3-25:
var fridge = {
lapte bătut: [1/3, "ceașcă"],
chefir: [1 + 1/2, "ceașcă"],
iaurt: [4, "ceașcă"],
};
var aluat = {
hardWhiteWhiteWholeWheatFloare: [ 2,
" cup"], zahăr: [1/3, "cup"],
madagascarVanilla: [1, "tsp"],
orangeZest: [1, "tbs"],
sifon: [1, "tsp"],
tartru: [ 1, " linguriță"], suc
de portocale: [1/2,
"ceașcă"], lapte de cultură:
[1/2, "ceașcă"], ou: [1],
merișoare: [2/3, "ceașcă"]
};
dough.culturedMilk[0] = f r i d g e . buttermilk[0] >= 1/2 ? (fridge.buttermilk[0] -= 1/2, 1/2) :
fridge.kefir[0] >= 9/16 ? (fridge.kefir[0] -= 9/16, 9/16) :
93
CAPITOLUL 3 ■ OPERATORI

frigider.iaurt[0] >= 10/16 ? (frigider.iaurt[0] -= 10/16, 10/16) :


alert("Fără pâine cu merișoare pentru tine!");
aluat.lapte de cultură;
// [0.5625, "ceașcă"]
fridge.kefir;
// [0.9375, "cup"]

Figura 3-25. Efectuarea a două expresii ca fiind una singură cu ajutorul operatorului ,

Ștergerea unui membru, element sau variabilă


Dacă nu avem lapte bătut, chefir sau iaurt, putem face totuși pâine cu afine, dublând sucul de portocale.
Cum îi spunem lui JavaScript să șteargă aluat.lapte de cultură și apoi să dubleze aluat.suc de portocale?
Pentru a șterge membrul CulturedMilk din aluat, îl vom trece la operatorul delete, care
funcționează cu orice variabilă, membru, element sau parametru. Pentru a dubla cantitatea de OJ, vom
trece 2 la operatorul *=, pe care l-am abordat anterior. Le putem face pe amândouă într-o singură
instrucțiune, separându-le cu operatorul , virgulă.
Pentru a verifica dispariția lui CulturedMilk, îl putem trece la operatorul typeof, care returnează
tipul de valoare al operandului său sub forma unui șir de caractere. Astfel, pentru un membru lipsă,
cum ar fi culturedMilk, typeof ar returna "undefined". Sau am putea verifica dispariția acestuia cu in,
un operator care returnează true dacă un obiect conține un membru numit cu un anumit șir de
caractere. În caz contrar, in returnează false. Rețineți că typeof are câteva ciudățenii. Pentru null,
typeof returnează "object", nu "null", iar pentru o funcție, typeof returnează "function", nu "object".
Da, destul de prostesc. Dar încercați, verificând munca dvs. cu ajutorul figurii 3-26.

var aluat = {
hardWhiteWhiteWholeWheatFloare: [2,
" ceașcă"], zahăr: [1/3, "ceașcă"],
94
CAPITOLUL 3 ■ OPERATORI

madagascarVanilla: [1, "tsp"],


orangeZest: [1, "tbs"],
sifon: [1, "tsp"],
tartru: [ 1, " linguriță"], suc
de portocale: [1/2,
"ceașcă"], lapte de cultură:
[1/2, "ceașcă"], ou: [1],
merișoare: [2/3, "ceașcă"]
};
delete d o u g h . culturedMilk, d o u g h .orangeJuice[0] *= 2, dough.orangeJuice;
// [1, "cup"]
typeof dough.culturedMilk;
// "undefined"
"culturedMilk" în aluat;
// fals
"orangeJuice" în aluat;
// adevărat

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.

■ Notă Capitolul 6 tratează mai pe larg funcțiile.

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.

Scrierea unei condiții if


Adesea, veți dori ca JavaScript să ruleze o cale dacă circumstanțele permit acest lucru, dar altfel să nu facă
nimic și să treacă mai departe. instrucțiunile condiționale if vor fi pâinea și untul pentru acest tip de
decizie. Pentru a scrie una dintre aceste instrucțiuni, este suficient să introduceți cuvântul cheie if, urmat
de o expresie între paranteze și apoi de o cale sub forma unei instrucțiuni sau a unui bloc copil. În cazul în
care expresia nu returnează u n boolean, JavaScript va converti valoarea în boolean, trecând-o la Boolean(),
pe care am explorat-o în capitolul 2. Așadar, dacă expresia returnează orice altă valoare în afară de
undefined, null, "", 0, NaN sau false, JavaScript are undă verde pentru a executa calea.
Prin urmare, un interpretor JavaScript vede o condiție if astfel:
if (Boolean(expression)) path
Dar tu scrii așa:
if (expresie) path
Deschideți firebug.html în Firefox și apoi apăsați F12 pentru a activa Firebug. Dacă abia ați venit la
noi, întoarceți-vă la Prefață pentru detalii despre cum să faceți acest lucru.
Pentru orice alergare rapidă pe care o fac, am tendința de a purta Nike Mayfly, care cântărește
doar patru uncii. Prin comparație, majoritatea pantofilor de alergare cântăresc de trei sau patru ori mai
mult. Cu toate acestea, partea negativă a designului minimalist al Mayfly este că amortizarea sa se
stinge după doar 100 de kilometri.
Să creăm un obiect mayfly care să conțină două metode care pot interoga o variabilă secretă
numită tally, care va conține o numărătoare a kilometrilor parcurși într-o pereche de pantofi Mayfly.
mayfly.addToTally() adaugă parametrul său (numit km) la tally numai dacă km este sigur pentru
adăugare - adică dacă km este de tipul de date număr, dar nu este de tipul numerelor speciale NaN sau
Infinit. Cealaltă metodă, mayfly.kmLeftToLive(), va returna un mesaj care indică câți kilometri de
amortizare mai are Mayfly doar dacă tally este mai mic de 100.
Deci, în Firebug, introduceți următorul cod și faceți clic pe Run. În acest fel, se creează o
închidere, astfel încât tally să poată fi interogat numai de addToTally() și kmLeftToLive(). Închiderile
sunt tratate în Capitolul 6, așa că, deocamdată, dați din cap în cunoștință de cauză. Oricum,
concentrați-vă asupra celor două condiții if.
var mayfly = function () {
var tally = 0;
retur {
98
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

addToTally: function (km) {


if (typeof km === "number" && isFinite(km)) {
return tally += km;
}
},
kmLeftToLive: function () {
if (tally < 100) {
return "Mayfly mai are " + (100 - tally) + " kilometri de trăit.";
}
}
}
}();
Acum că am inițializat mayfly() și variabila secretă tally, faceți clic pe Clear în colțul din dreapta
jos al Firebug. Procedând astfel, nu numai că lăsăm mayfly() în memorie, astfel încât să o putem
invoca, dar ne împiedică să o suprascriem de următoarele ori când vom face clic pe Run.
Acum să adăugăm 10 kilometri la numărătoare. Pentru a face acest lucru, treceți 10 în
mayfly.addToTally() introducând următorul cod și făcând clic pe Run:
mayfly.addToTally(10);
// 10
Putem adăuga 10 la tally pentru că am trecut un număr care trece testul în primul if
declarație.
Faceți clic pe Run (Aleargă) de încă trei ori pentru a înregistra alte câteva curse de 10 km. Deci acum,
așa cum ilustrează figura 4-1, numărătoarea noastră de variabile secrete conține 40.

Figura 4-1. Invocarea mayfly.addToTally de patru ori cu parametrul 10

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".

Adăugarea unei clauze else


Acum, dacă doriți ca JavaScript să meargă pe o cale atunci când o expresie este adevărată, dar pe o
altă cale atunci când este falsă? Pur și simplu adăugați o clauză else la condiția if. Pentru a face
acest lucru, tastați pur și simplu cuvântul cheie else și apoi o cale sub forma unei declarații sau a unui
bloc copil. JavaScript va rula calea if atunci când expresia booleană este adevărată și calea else
atunci când este falsă. Așadar, una sau cealaltă cale va fi executată, dar niciodată ambele.
În prezent, atât mayfly.addToTally(), cât și mayfly.kmLeftToLive() returnează undefined ori de câte
ori JavaScript nu execută căile if. Să schimbăm acest lucru adăugând clauze else la ambele metode.
Pentru a face acest lucru, faceți clic pe Clear în Firebug, introduceți următoarele, apoi faceți clic pe
Run:
var mayfly = function () {
var tally = 0;
retur {
addToTally: function (km) {
if (typeof km === "number" && isFinite(km)) {
return tally += km;
} else {
return "Invalid parameter!";
}
},
kmLeftToLive: function () {
if (tally < 100) {
return "Mayfly mai are " + (100 - tally) + " kilometri de trăit.";
} else {
return "Mayfly is dead!";
}
}
}
}();
Acum faceți clic pe Clear (Ștergeți) și introduceți următoarele:
mayfly.addToTally("zece");
// "Parametru invalid!"
Aici, typeof km returnează "string", astfel încât operatorul === returnează fals, iar operatorul &&
returnează fals. Prin urmare, JavaScript merge pe calea else, iar mayfly.addToTally() returnează
"Invalid parameter!" în loc să adauge km la tally, care rămâne la 0. Să verificăm acest lucru făcând clic
pe Clear și apoi invocând mayfly.kmLeftToLive() astfel:
mayfly.kmLeftToLive();
// "Mayfly mai are 100 de kilometri de trăit".
Minunat, deci Mayfly-ul nostru are încă rezervorul plin. Acum să ne asigurăm că clauza else pentru
mayfly.kmLeftToLive() funcționează dacă faceți clic pe Clear și apoi executați următoarele:
mayfly.addToTally(110);
mayfly.kmLeftToLive();
// "Mayfly a murit!"
100
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

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!

A împacheta sau a nu împacheta


După cum s-a menționat mai devreme, ori de câte ori o instrucțiune compusă conține o singură
instrucțiune copil, nu trebuie să o înfășurați în acolade. Astfel, am fi putut defini mayfly în felul
următor, unde codul în bold arată că declarațiile cu un singur copil nu au paranteze curly brace:
var mayfly = function () {
var tally = 0;
retur {
addToTally: function (km) {
if (typeof km === "number" && isFinite(km)) return
tally += km;
altfel
return "Invalid parameter!";
},
kmLeftToLive: function () {
dacă (tally < 100)
return "Mayfly mai are " + (100 - tally) + " kilometri de trăit."; else
return "Mayfly is dead!";
}
}
}();
În plus, am fi putut omite și pauzele de linie:
var mayfly = function () {
var tally = 0;
retur {
addToTally: function (km) {
if (typeof km === "number" && isFinite(km)) return tally += km;
else return "Invalid parameter!";
},
kmLeftToLive: function () {
if (tally < 100) return "Mayfly mai are " + (100 - tally) + " kilometri de trăit."; else
return "Mayfly este mort!";
}
}
}();
Idiomul else if, pe care îl vom aborda în continuare, profită de această caracteristică JavaScript de o
singură linie, fără paranteze. În plus, veți întâlni ambele stiluri în scripturi scrise de alții, inclusiv de mine.
Cu toate acestea, ca începător, este posibil să doriți să înfășurați declarațiile simple copil în paranteze
curly în măsura în care acest lucru elimină necesitatea de a vă aminti că două sau mai multe declarații
copil trebuie să fie înfășurate în paranteze curly și că o clauză else merge cu cea mai apropiată
condiție if.
101
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

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)!

Codificarea mai multor căi de acces cu expresia else if


Am tendința de a avea un smoothie ca desert în majoritatea serilor. Uneori, dacă este târziu, asta este
tot ce mănânc. Nu-mi fac prea multe griji în privința caloriilor. Smoothie-ul meu favorit plin de
energie conține iaurt cu cremă Brown Cow, smântână și lapte din iarbă, scorțișoară Saigon și afine
sălbatice. Dacă îți place iaurtul, fă-ți plăcerea de a lua cândva un iaurt Brown Cow cream-top. Crede-
mă, nu vei uita niciodată primul tău Brown Cow!
Bine, a ieșit greșit. Oricum, pentru Brown Cow și pentru smântâna și laptele hrănite cu iarbă,
trebuie să merg la Whole Foods. Dar nu există decât unul singur în Pittsburgh și, pentru a ajunge acolo,
trebuie să trec prin "murder alley". În cele mai multe nopți, când deschid frigiderul, nu există Brown
Cow și trebuie să aleg un alt iaurt sau chefir pentru smoothie-ul meu. În ordinea descrescătoare a
preferințelor, acestea sunt Stonyfield cream-top, Fage cultured cream și Lifeway Greek-style kefir.
În regulă atunci, să creăm un obiect numit fridge cu membri booleeni care să indice care sunt
opțiunile mele de lapte de cultură. Viața este bună în seara asta, pentru că am Brown Cow acolo:
var fridge = {
brownCow: t r u e ,
stonyfield: false,
fage: true,
lifeway: false
};
var smoothie;
Acum dorim ca JavaScript să aleagă iaurtul sau chefirul meu preferat disponibil, testând următoarele
patru expresii în ordine de sus în jos:
frigider.brownCow
frigider.stonyfield
frigider.fage
frigider.lifeway
Dar o condiție if poate testa doar o singură expresie. Deci, scriem patru din acestea la rând? Este
o soluție cam greoaie, dar să o facem în Firebug. Rețineți că, pentru ca acest lucru să funcționeze,
trebuie să testăm cele patru expresii de jos în sus, astfel încât cel mai bun iaurt disponibil să ajungă în
smoothie-ul meu:
var fridge = {
brownCow: t r u e ,
stonyfield: false,
fage: true,
lifeway: false
};
var smoothie;
if (fridge.lifeway) {
smoothie = "Lifeway Greek-style kefir";
}
if (fridge.fage) {

102
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

smoothie = "Cremă cultivată Fage";


}
if (fridge.stonyfield) {
smoothie = "Stonyfield cream-top yogurt";
}
if (fridge.brownCow) {
smoothie = "Iaurt cu cremă de vacă maro";
}
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."
Deși acest artificiu funcționează, ne face să părem niște nepricepuți în fața programatorilor
cunoscători de JavaScript, deoarece testăm fiecare variație, chiar dacă am găsit anterior o potrivire.
Există o modalitate mai bună, nu-i așa?
Da, sigur că da. Mai întâi, reordonați condițiile if în ordinea descrescătoare a preferințelor. În al
doilea rând, aninați condițiile if de la doi la patru într-o clauză else pentru condițiile if de la unu la
trei. Acum avem câteva opțiuni de excludere în cazul în care o condiție if este adevărată; cu alte
cuvinte, nu continuăm și nu testăm fiecare variație după ce o condiție if se dovedește a fi adevărată.
var fridge = {
brownCow: t r u e ,
stonyfield: false,
fage: true,
lifeway: false
};
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";
}
}
}
}
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."
Acest lucru este mai elegant, dar putem face și mai bine dacă folosim expresia else if. Așadar,
faceți clic pe Clear în ambele panouri Firebug, introduceți și executați următoarele:
var fridge = {
brownCow: t r u e ,
stonyfield: false,
fage: true,
lifeway: false
};
var smoothie;
103
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.

Figura 4-2. Simplificarea lucrurilor cu idiomul else if

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."

Figura 4-3. Testarea căii implicite else path

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

smoothie = "Fage cultured cream";


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."

Controlul fluxului cu ajutorul expresiilor condiționate


În cazul în care clauzele if și else conțin instrucțiuni de expresie unică, puteți controla mai elegant
fluxul cu o expresie condițională folosind operatorul ?:, pe care l-am abordat în capitolul 3.
În plus, puteți să aninați expresii condiționale pentru a imita și expresia else if.

■ 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

Figura 4-4. Înlocuirea unei instrucțiuni if else cu o expresie ?:.

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ă.

Luarea uneia dintre cele mai multe căi cu un comutator


Acum, pentru un mod mai puțin obișnuit de a scrie o ramificare cu mai multe direcții, să ne uităm la
instrucțiunea switch. În mod obișnuit, instrucțiunile switch sunt utilizate dacă toate căile depind de
valoarea aceleiași expresii și dacă expresia respectivă returnează un șir de caractere sau un număr.
Din punctul de vedere al păsării, declarațiile if și switch arată similar:
if (expresie) {block} switch
(expresie) {block}
Dincolo de asta, dacă și comutatorul sunt net diferite. În primul rând, blocul if conține doar o
singură cale, în timp ce blocul switch conține mai multe căi. Acestea sunt marcate de una sau mai
multe expresii de caz. JavaScript decide ce cale să urmeze prin compararea expresiei switch cu
expresiile case cu ajutorul operatorului ===. Așadar, nu are loc nicio conversie de tip de date, așa cum
ar avea loc cu operatorul ==.
Pe de altă parte, expresiile case marchează doar locul în care JavaScript începe să ruleze declarațiile din
comutator
bloc. Trebuie să marcați manual sfârșitul fiecărui traseu cu o instrucțiune de întrerupere sau de
întoarcere disruptivă. Procedând astfel, se împiedică JavaScript să ruleze toate căile din aval de
expresia de caz corespunzătoare.
În cele din urmă, în timp ce o clauză else conține calea de cădere pentru o instrucțiune if, o clauză
default case conține calea de cădere pentru un comutator. Cu toate acestea, la fel ca else, calea
implicită este opțională.
Așadar, reîmprospătați Firefox pentru a șterge din memorie tot ce am codat până acum. Apoi faceți
clic pe Clear în Firebug pentru a face tabula rasa și să încercăm un schimb. Pentru echipa ta sportivă
preferată, să spunem că vrei ca JavaScript să returneze numele unui jucător pe baza unui număr de
tricou. Deoarece toate căile depind de aceeași expresie, care returnează un număr, switch-ul este mai
eficient decât else if. Pentru Pittsburgh Steelers, un comutator pentru numerele de tricou ar arăta
astfel. Nu ezitați să alegeți echipa dvs. preferată, în loc de a mea.
var jersey = 34, name = "";
switch (jersey) {
cazul 7:
name = " Roethlisberger";

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

// "Numărul 92 este Harrison."


Verificați munca dvs. cu figura 4-6.

Figura 4-6. Trecerea de la o clauză de caz la alta

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

Scrierea unei bucle while


Pentru a elimina corvoada de a codifica o mulțime de instrucțiuni condiționale identice una după alta,
JavaScript vă oferă patru instrucțiuni de buclă. Amintiți-vă de mai devreme că acestea sunt while, do
while, for și for in. Vom explora fiecare dintre acestea pe rând, începând cu bucla simplă while.
buclele while sunt ca o condiție if care se execută la nesfârșit până când expresia sa se întoarce
falsă. Nu este surprinzător faptul că, din punct de vedere sintactic, instrucțiunile while și if par
similare:
if (expresie) calea
while (expresie) calea
La fel cum JavaScript convertește valoarea expresiei unei condiții if într-o expresie booleană,
dacă este necesar, face același lucru și pentru expresia unei bucle while. Deci, pentru JavaScript, planul
de joc arată astfel:
while (Boolean(expresie)) path
Prima dată când JavaScript execută o instrucțiune while, dacă expresia returnează true sau o valoare
care se convertește în true, atunci calea se execută. Pe de altă p a r t e , în cazul în care expresia returnează
false sau o valoare care se convertește în false (rețineți că aceste valori sunt undefined, null, "", 0 sau
NaN), atunci calea nu se execută. Altfel spus, la prima iterație, o buclă while nu diferă de o condiție if.
Acum, dacă traseul a funcționat la prima iterație, JavaScript trebuie să decidă dacă trebuie să mai
facă o altă rundă a traseului. Pentru a face acest lucru, pur și simplu reevaluează expresia buclei while.
În cazul în care expresia returnează din nou o valoare de adevăr, calea se execută. Dar în cazul în care
expresia returnează o valoare falsă, calea nu se execută, astfel încât JavaScript trece de bucla while și
continuă cu restul scriptului.
Iterațiile buclei while continuă până când expresia acesteia returnează o valoare falsă. Având în
vedere acest lucru, trebuie să vă asigurați că, în cele din urmă, expresia returnează o valoare falsă. În
caz contrar, bucla nu se va opri niciodată din iterație. O astfel de greșeală este denumită în mod
corespunzător o buclă infinită. Acestea îngheață browserul până când este atinsă limita de scripturi cu
durată lungă de execuție, de obicei între 5 și 10 secunde.
În esență, dorim să scriem o condiție if, dar undeva pe parcurs să ne asigurăm că expresia va
returna în cele din urmă o valoare falsă. De obicei, acest lucru se face prin incrementarea unei variabile de
buclă, denumită în mod tradițional i, j sau k, pe care, la rândul său, o comparați cu numărul de ture pe
care doriți să le facă JavaScript. Așadar, faceți clic pe Clear în ambele panouri Firebug și haideți să
introducem și să rulăm o simplă buclă while.
Nu știu ce părere aveți voi, dar o ceașcă de ceai îmi înviorează starea de spirit. Așadar, dacă mă
simt puțin posomorâtă și vreau să cotrobăi prin ceaiurile în foi libere din cămară în căutarea Borpatra,
ceaiul meu Assam preferat, aș putea să fac acest lucru cu următoarea buclă de timp. Așadar, introduceți
și rulați următoarele:
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ă;

116
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

}
i ++;
}
"Mă simt " + starea de spirit + " !";
// "Mă simt veselă!"
Verificați munca dvs. cu ajutorul figurii 4-8.

Figura 4-8. Iterarea peste un tablou cu o buclă while

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".

Abandonarea unei iterații, dar nu și a buclei


O instrucțiune break îi spune lui JavaScript să întrerupă complet o buclă. Dar ce se întâmplă dacă doriți
să întrerupeți doar o iterație, dar nu și bucla? Există ceva mai puțin draconic decât break? Se pare că
da. Instrucțiunile continue termină pur și simplu o iterație. JavaScript reevaluează apoi expresia
booleană pentru a vedea dacă se face o altă rundă.
În mod obișnuit, continue este utilizat pentru a întrerupe o iterație atunci când o variabilă conține
o valoare nedorită, cum ar fi undefined sau "". Să corupem looseLeafTea adăugând "" înainte de
"Kenilworth" și undefined înainte de "Keemun" (prin inserarea a două virgule la rând). Apoi adăugăm o
clauză else if care se execută ori de câte ori un element din looseLeafTea conține o valoare falsă.
Acolo vom face două lucruri. În primul rând, vom șterge elementul
118
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

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

Înlocuirea Break cu Return într-o funcție


La fel cum puteți anula un comutator cu return în loc de break ori de câte ori apare într-o funcție,
puteți anula o buclă (while, do while, for, for in) cu return în loc de break ori de câte ori apare într-o
funcție. Așadar, faceți clic pe Clear în ambele panouri Firebug și să rescriem bucla noastră while în
interiorul unei funcții, înlocuind break cu return:
var looseLeafTea = [
"Ghillidary",
"Kenilworth",
"Milima",
"Keemun",
"Boisahabi",
"Manohari",
"Borpatra",
"Lukwah",
"Khongea"
];
function findTea(tea) {
var i = 0;
while (i < looseLeafTea.length) {
if (looseLeafTea[i] === tea) {
returnează "vesel";

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.

■ Notă Capitolul 6 tratează mai pe larg funcțiile.


121
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

Scrierea unei bucle do while


buclele while oferă o modalitate de a rula condiționat o cale de zero sau mai multe ori. Adică, dacă
expresia buclei este falsă atunci când este evaluată pentru prima dată, atunci traseul nu va fi parcurs
deloc. Dar dacă doriți să vă asigurați că traseul se execută cel puțin o dată? În acest caz, veți scrie o
buclă "do while". Sintaxa pentru do while este următoarea:
do path while (expresie);
După cum probabil ați ghicit până acum, calea poate fi fie o singură instrucțiune copil, fie un
bloc, iar valoarea expresiei este convertită în boolean, dacă este necesar, prin trecerea ei la Boolean().
Deși este ușor de trecut cu vederea, punctul și virgula care urmează expresiei între paranteze este
necesar. Ținând cont de aceste lucruri, faceți clic pe Clear în ambele panouri Firebug și haideți să
încercăm o buclă do while.
De cele mai multe ori, când vine timpul să urmez o rețetă, există un condiment care, dacă nu ar fi
disponibil, mi-ar distruge planurile. Să zicem că vreau să fac scones cu lămâie; ingredientul limitativ ar
fi, în mod ciudat, coaja de lămâie. Fiind o gurmandă, se poate presupune că am cel puțin un condiment.
Prin urmare, are sens să scotocesc prin raftul de condimente cu o buclă de tip "do while", astfel, pentru
că vrem să verificăm cel puțin un condiment:
var condimente
=
["scorțișoară"
, "ghimbir",
"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;
face {
if (spices[i] === "coajă de lămâie")
{ putTheKaiboshOn = false;
break;
}
i ++;
} while (i < spices.length);
(putTheKaiboshOn) ? "Nu se poate face!" : "Dă-i d r u m u l !";
// "Dă-i d r u m u l !"
Verificați munca dvs. cu ajutorul figurii 4-11.
122
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

Figura 4-11. Căutarea în matricea de condimente cu o buclă do while

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!

Scrierea unei bucle for


Cititorii atenți vor observa că, atât în exemplul while, cât și în exemplul do while, am inițializat o
variabilă de buclă înainte de buclă și apoi am incrementat-o chiar la sfârșitul parcursului buclei. Este
un pic cam enervant să trebuiască să ne amintim să inițializăm și să creștem o variabilă de buclă, nu
credeți? Așa crede și JavaScript.
În consecință, există un al treilea tip de instrucțiune de buclă, for, care plasează inițializarea în stânga
expresiei booleene și creșterea în dreapta. Rețineți că punct și virgulă separă expresiile de inițializare,
booleană și de creștere.
Faceți clic pe Clear în ambele panouri Firebug și să încercăm o buclă for.
Norah Jones este una dintre artistele mele preferate. Astăzi am ascultat o versiune preliminară a
noului ei album, The Fall. Mă gândesc că "Back to Manhattan" va fi un hit. În orice caz, pentru mine
așa este. Cu toate acestea, deoarece albumul este nou, îmi este greu să-mi amintesc numărul piesei
"Back to Manhattan".
Să creăm o matrice numită theFall care conține o listă cronologică a pieselor de pe The Fall și
apoi să punem JavaScript să le parcurgă cu o buclă for în căutarea piesei "Back to Manhattan", pe care
o vom salva într-o variabilă numită song. JavaScript va adăuga apoi 1 la indexul elementului
corespunzător și va stoca rezultatul în j, care reprezintă numărul piesei. Procedăm astfel deoarece
elementele tablourilor sunt numerotate cu numere întregi care încep cu 0, în timp ce piesele albumelor
sunt numerotate, evident, cu numere întregi care încep cu 1.
Introduceți și rulați următoarea buclă for, apoi verificați-vă activitatea cu ajutorul figurii 4-13:
125
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

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."

Figura 4-13. Iterarea peste theFall cu o buclă for

Aici, JavaScript inițializează i și j la 0 înainte de a evalua expresia booleană i < theFall.length.


După orice rundă a buclei for, JavaScript îl incrementează pe i cu ajutorul operatorului ++. Apoi
reevaluează expresia booleană pentru a vedea dacă ar trebui să ruleze din nou calea. Rețineți că
expresia
126
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

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.

Enumerarea membrilor cu o buclă for in


A patra și ultima instrucțiune de buclă, for in, oferă o modalitate de a enumera membrii unui obiect.
JavaScript se asigură că expresia booleană, care utilizează operatorul in din capitolul 3, returnează
true prin atribuirea numelui unui membru diferit la operandul stâng al lui in înainte de o iterație. Nu
este nevoie să inițializați și să incrementați o variabilă de buclă pentru a preveni o buclă infinită;
JavaScript știe deja să facă o iterație pentru fiecare membru.
for (member in object) path
Există trei lucruri de remarcat. În primul rând, indiferent dacă membrul a fost numit cu un
identificator sau cu un șir de caractere, JavaScript returnează numele acestuia sub forma unui șir de
caractere. În al doilea rând, operandul din stânga la in poate fi o variabilă, un membru sau un element.
Altfel spus, poate fi orice lucru căruia îi puteți atribui un șir de caractere. În al treilea rând, operandul
drept al lui in poate fi orice expresie pentru un obiect - de obicei, un identificator sau o invocare de
funcție. Ținând cont de aceste lucruri, faceți clic pe Clear în ambele panouri Firebug și haideți să
lucrăm la un exemplu de buclă for in.
Îmi place să port un pantof adaptat la ritmul și distanța alergării. Așadar, am în pivniță mai mulți
pantofi de alergare. Sunt opt Nike acolo jos în acest moment. Să creăm un obiect numit pantofi care să
conțină greutatea în uncii a fiecărui pantof cu care aș putea alerga în această seară:
var shoes = {
"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
}
În general, cu cât un pantof cântărește mai mult, cu atât oferă mai multă amortizare. Prin urmare, dacă alerg
mult
pe teren, port o pereche de pantofi care cântăresc mai mult de 10 uncii. Să enumerăm membrii din
pantofi, salvând numele oricărui pantof care cântărește mai mult de 10 uncii într-o matrice numită
myOptions. În acest fel, voi ști care sunt opțiunile mele.
Rețineți că, în timpul fiecărui tur al buclei for in, JavaScript atribuie numele unui membru din
shoes la shoe. Astfel, shoes[shoe] va returna greutatea unui pantof. În cazul în care această greutate
este mai mare sau egală cu 10, adăugăm numele pantofului la myOptions prin intermediul metodei push(),
pe care o voi aborda în capitolul 5.
Încercați să introduceți și să rulați următoarea buclă for in în Firebug, apoi verificați munca dvs. cu
Figura 4-
14:
var shoes = {

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"]

Figura 4-14. Enumerarea membrilor cu o buclă for in

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

if (shoes["Free"]] >= 10) {


myOptions.push("Free");
}
if (shoes["Mayfly"] >= 10) {
myOptions.push("Mayfly");
}
if (shoes["Zoom Vomero"] >= 10) {
myOptions.push("Zoom Vomero");
}
if (shoes["LunarElite"] >= 10) {
myOptions.push("LunarElite");
}

Condiționale mai rapide


Acum că am explorat condiționalele și buclele, haideți să analizăm câteva tehnici pentru a le face să
ruleze mai rapid - nu doar puțin, ci de două până la șapte ori mai rapid, dacă nu chiar mai mult. În
măsura în care cea mai mare parte a oricărui comportament JavaScript este alcătuită din condiționale
și bucle, dacă faceți acest lucru, comportamentele dvs. vor părea mult mai receptive pentru vizitatori.
Da, și mie îmi place cum sună asta!
Să începem cu câteva modalități de a codifica mai rapid comutatoarele și condiționalele if else.
Un lucru simplu pe care îl puteți face este să preferați switch în locul if else ori de câte ori codificați
cinci sau mai multe căi pe care JavaScript să le urmeze în mod condiționat. După patru, adăugarea
incrementală a condițiilor la un switch scade viteza mult mai puțin decât dacă faceți acest lucru pentru
un if else.
OK, a fost nedureros. La fel este și a doua modalitate de a codifica condiționale mai rapide - doar
ordonați-le căile de la cea pe care JavaScript are cele mai multe șanse să o urmeze la cea pe care
JavaScript are cele mai puține șanse să o urmeze. Procedând astfel, se minimizează numărul de expresii
booleene pe care JavaScript trebuie să le evalueze.
Acum, pentru a treia modalitate de a codifica condiționale mai rapide: nu le codificați în primul
rând. Nu, nu este o greșeală de scriere. În cazul în care ați scris un if else sau un comutator pentru
variații ale unui șir de caractere sau ale unui număr și fiecare cale face același lucru (doar că are o
valoare diferită a acelui șir sau număr), înlocuirea if else sau comutatorului cu o interogare a unui array
sau a unui membru al unui obiect va duce la un câștig de viteză extraordinar, ca să spunem așa.
Există câteva motive pentru care acest lucru este preferabil instrucțiunilor condiționale. În primul
rând, JavaScript nu trebuie să detalieze prin condiții, ci doar printr-o singură operațiune []. Pe de
altă parte, în timp ce adăugarea de condiții la un switch sau if else scade viteza de evaluare, adăugarea
de membri la un obiect sau de elemente la un array nu scade viteza de interogare.
În mod ideal, sunteți convins să înlocuiți condiționalele cu un obiect sau o interogare de tip array
ori de câte ori este posibil. Acum să încercăm tehnica. Curățați de două ori Firebug, apoi introduceți
următoarea funcție, care conține un comutator care mapează numerele de tricouri cu numele
jucătorilor pentru Ziua de deschidere din 2010, în timp ce Pittsburgh Pirates încearcă să evite al
optsprezecelea sezon de înfrângeri la rând. În măsura în care 17 din 25 de Pirați nu au fost în banca de
rezerve în aprilie anul trecut, namePirate() va fi utilă!
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";

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

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";
break;
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 + " .";
}
namePirate(46);
// "46 este Garrett Jones."

132
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

Figura 4-15. Maparea numerelor de tricouri cu nume pentru Pittsburgh Pirates cu ajutorul unui comutator

Acum, am ordonat comutatorul ca pe o tablă de scor (aruncători, prinzători, jucători de câmp,


jucători de câmp) și apoi după numele de familie. JavaScript a trebuit să evalueze 21 de clauze de caz
pentru a determina cine poartă 46. Dar dacă am înlocui comutatorul cu o interogare cu un membru al
obiectului, am înlocui la rândul nostru acele 21 de operații === cu 1 operație []. Da, și mie mi se pare
o idee bună.
Deci, ștergeți de două ori Firebug, apoi introduceți următorul obiect literal. Rețineți că, deși
adăugăm membrii în aceeași ordine ca în comutator, adică după poziție și apoi după nume, amintiți-vă
că ECMAScript nu definește o ordine pentru membrii obiectului. Astfel, nici D. J. Carrasco și nici un
alt pirat nu este primul membru din pirați. Rețineți, de asemenea, că, din moment ce numerele de
tricouri sunt numere întregi, am fi putut face acest lucru cu un array. Cu toate acestea, ar fi fost foarte
greu de tastat. În măsura în care un obiect poate avea elemente la fel ca o matrice, o caracteristică pe
care o voi explora în capitolul 5, nu are rost să facem toată tastatura suplimentară pentru a crea o
căutare de matrice.
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",
133
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

"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"
};
Acum să codăm o versiune mult mai simplă a namePirate(). Acum există doar o singură linie de cod
în corp:
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") + " .";
}
În regulă, să testăm acest lucru și să vedem dacă funcționează. Hmm. Cineva care poartă 3 joacă la baza a
doua acum.
că Freddy Sanchez a fost vândut la Giants pentru perspective. Haideți să aflăm cine este pe locul doi (nu, nu
134
CAPITOLUL 4 ■ CONTROLUL FLUXULUI

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

Figura 4-16. Înlocuirea unui comutator cu o interogare a unui membru al obiectului

Acum că știți trei modalități de a face ca condiționalele să funcționeze rapid, să explorăm


câteva modalități de a face acest lucru pentru bucle.

Bucle Snappier Loops


Deci, care dintre cele patru bucle este cea mai rapidă?
Nu, nu pentru.
Nici cât timp.
Categoric nu pentru in. Ăla aleargă ca o broască
țestoasă. Deci, atunci, trebuie să fie de durată, nu?
Îmi pare rău, nu.
În regulă, deci aceasta a fost o întrebare capcană. buclele for in sunt cele mai lente. Uneori sunt
de șapte ori mai lente decât celelalte. Dar buclele while, for și do while merg cap la cap. Așadar,
dintre cele trei, codificați-o pe cea pe care o preferați pentru o anumită sarcină.
Pe de altă parte, încercați să înlocuiți for în bucle cu unul dintre celelalte trei ori de câte ori
puteți. Totuși, acest lucru poate părea o sarcină dificilă. La urma urmei, for in este singura buclă care
poate enumera membrii unui obiect, nu-i așa?
Da și nu: buclele for in sunt singurele care pot enumera membrii necunoscuți ai obiectului, cu
alte cuvinte, membrii cărora nu le cunoașteți numele. Cu toate acestea, puteți enumera membrii al
căror nume îl cunoașteți cu una dintre celelalte trei bucle. Pur și simplu puneți aceste nume într-o
matrice și treceți-le în revistă.
136
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

Figura 4-17. Înlocuirea unui for în sloth cu un for gazelle

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

Figura 4-18. Bucla for mai rapidă

Î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

var d = topTwenty[p1] - topTwenty[p2];


if (d== 0) {
return d;
} else {
return (p2 < p1) ? -1 : 1;
}
});
var i = rocketRichard.length - 1;
do {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]]] + ", ";
} while (i --);
note.slice(0, -2);
// "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
Deci, iată. Am terminat de explorat condiționalele și buclele. Apropo, Sidney Crosby și Steven
Stamkos au terminat la egalitate pentru cele mai multe goluri în NHL, cu 51 de goluri fiecare. Ei vor
împărți trofeul Rocket Richard. Notă pentru NHL: Aș sugera ca, pe viitor, cel mai mic număr de goluri
în poarta goală să fie criteriul de departajare pentru premiul Rocket Richard. Crosby a avut un singur gol
cu poarta goală în acest an, în timp ce Stamkos a avut cinci, inclusiv numărul 51!

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ă.

Crearea obiectelor cu un constructor


Ben & Jerry's Wild Maine Blueberry se odihnește liniștit într-un mic cimitir pe o colină cu iarbă
înconjurată de un gard alb din Waterbury, Vermont. Epitaful de pe umila sa piatră de mormânt spune
următoarele:

Afine sălbatice Maine


Blueberry Afine sălbatice
Maine Blueberry
De pe tărâmul puffinului,
Acum, când tânjim după
tine, Ne întoarcem la
brioșă.
1990-92

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

• 1 ceașcă de smântână pentru frișcă Organic Valley

• 1 ceașcă de organic Valley half & half

• 5/8 cană de zahăr

• 6 gălbenușuri de ou

• 2 căni, afine sălbatice din Maine


• 1 păstaie de vanilie Bourbon din Madagascar

• 2 lingurițe de suc proaspăt de lămâie


Deschideți firebug.html în Firefox, apoi apăsați F12 pentru a activa Firebug. Dacă abia ați venit la
noi, întoarceți-vă la prefață pentru detalii despre cum să faceți acest lucru. Deci, oricum, putem
reprezenta această rețetă cu următorul obiect wildMaineBlueberry:
var wildMaineBlueberry = {
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],
afine: [2, "ceașcă", "afine proaspete sălbatice din Maine"]],
vanilie: [1, "boabă", "Madagascar Bourbon"],
freshLemonJuice: [2, "linguriță"]
};
Cu toate acestea, în afară de câteva luni, afinele proaspete din Maine sunt greu de găsit. Așa că, pentru
majoritatea
anului, ar trebui să setez wildMaineBlueberry.blueberries.blueberries fie la [2, "cup", " Dole frozen
wild blueberries"], fie la [2, "cup", "Wyman frozen wild blueberries"]. În plus, Madagascar Bourbon
este tipul meu preferat de boabă de vanilie. Dar dacă nu mai am așa ceva, voi pune la macerat una
tahitiană sau mexicană în schimb. Prima este mai blândă decât Madagascar Bourbon, în timp ce cea de-
a doua este mai intensă.
Deci, ce faci? Modificați manual wildMaineBlueberry.blueberries și wildMaineBlueberry.vanilla
ori de câte ori ingredientele mele preferate nu sunt disponibile? Ei bine, am putea, dar asta e atât de grețos.
Există o modalitate mai bună: creați un constructor care să producă pentru noi litri personalizați de Wild
Maine Blueberry. Iată cum:
Constructorii sunt funcții invocate cu ajutorul operatorului new. Spre deosebire de funcțiile obișnuite,
constructorii sunt numiți cu majuscule pentru a indica faptul că este necesar new. Dacă se omite new
pentru un constructor, se adaugă membri la obiectul global al ferestrei, ceea ce nu ar fi bine.
În orice caz, atunci când apelați un constructor cu new, JavaScript creează o variabilă privată
numită this, care conține un obiect gol la care puteți adăuga membri. În timp ce funcțiile returnează
în mod implicit un nedefinit, constructorii returnează implicit this. Astfel, nu trebuie să creați o
variabilă privată this în corpul constructorului sau să scrieți în mod explicit o instrucțiune de
returnare. JavaScript face acest lucru în locul dvs.
E foarte drăguț. Dar și mai bine, acesta moștenește orice membru pe care îl adăugați la obiectul
prototip al constructorului. Având în vedere toate acestea, putem crea un constructor numit
WildMaineBlueberry(). Membrii sculptați în piatră vor merge în WildMaineBlueberry.prototype, în
timp ce valorile pe care le trecem în parametrii numiți inteligent afine și vanilie vor fi atribuite
membrilor afine și vanilie ai acestui obiect. Cu alte cuvinte, stabilim prototipul pentru a conține
toate ingredientele care nu se schimbă niciodată și folosim constructorul pentru a personaliza
ingredientele care se schimbă, care sunt apoi combinate cu setul neschimbător.
În următorul constructor, puteți vedea cum folosim ?: pentru a seta valorile implicite ale afine și
vanilie, în cazul în care nu furnizăm un parametru afine sau un parametru vanilie (cu alte cuvinte,
valoarea parametrului afine este returnată de ?: dacă este setat; în caz contrar, este returnat șirul
implicit):
var WildMaineBlueberry = function(afine, vanilie) {
this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"]];

146
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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"]
};
Acum, să punem WildMaineBlueberry() să ne producă un litru de Ben & Jerry's Wild Maine
Blueberry cu valorile implicite preferabile pentru afine și vanilie. Apoi treceți acest lucru la metoda
console.dir() a Firebug, care, după cum arată figura 5-1, tipărește membrii proprii și moșteniți ai unui
obiect. Rețineți că membrii proprii sunt cei pe care îi adăugăm la acesta, deci afine și vanilie:
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 = new WildMaineBlueberry();
console.dir(wildMaineBlueberry);

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

this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];


scorțișoară &&& (this.scorțișoară = [1, "stick", "Saigon"]);
};
După ce am definit VanillaBean(), putem acum să adăugăm membri sculptați în piatră la
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, "ceașcă", "Organic
Valley"],

zahăr: [5/8, "ceașcă"],

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.

Acum să creăm un constructor Chocolate() care moștenește, de asemenea, membrii din


VanillaBean(). Parametrii opționali de cacao și ciocolată dulce-amăruie ne vor permite să specificăm
cacao olandeză și ciocolată dulce-amăruie, altele decât Callebaut, deși Callebaut va fi înlocuită în
cantități diferite pentru fiecare parametru, în cazul în care acestea lipsesc. Vom seta apoi prototipul, așa
cum am făcut pentru Coffee(). Cu toate acestea, vom adăuga un membru "gălbenușuri" pentru a-l
înlocui pe cel din VanillaBean.prototype, deoarece înghețata de ciocolată necesită mai puține
gălbenușuri decât cea de vanilie:
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
152
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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];

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();

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 chocolate = new Chocolate([1/4, "cup", "Bensdorp"]);
console.dir(chocolate);
153
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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

var MintChocolateChunk = function(mint) {


this.mint = mint || [1, "ceașcă", "frunze proaspete de mentă"];
};
MintChocolateChunk.prototype = new Chocolate();
Acum să adăugăm un membru vanilie pentru a suprascrie Chocolate.prototype.vanilla. În măsura în
care ciocolata dulce-amăruie Callebaut este mărunțită, avem nevoie doar de 1 cană, în loc de 1 1/2 cană.
Așadar, să modificăm primul element din matricea MintChocolateChunk.prototype.bittersweet. În cele
din urmă, nu m a i vrem cacao, așa că trecem acel membru la operatorul de ștergere, pe care l-am acoperit
în capitolul 3.
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];

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;

După ce am făcut acest lucru, să creăm o instanță MintChocolateChunk() numită


mintChocolateChunk, verificând munca noastră cu ajutorul figurii 5-7. Rețineți că mintChocolateChunk
are propriul membru mint și moștenește alți membri din MintChocolateChunk.prototype,
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) {
155
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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];

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 mintChocolateChunk = new MintChocolateChunk();
console.dir(mintChocolateChunk);

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

Determinarea tipului sau a tipurilor de care un obiect este o instanță


Lanțul de prototipuri, cum ar fi cele pe care le-am văzut mai devreme, determină tipul sau tipurile de
care un obiect precum mintChocolateChunk este o instanță, adică ce constructor sau constructori
moștenește un obiect. Pentru a afla acest lucru, veți utiliza operatorul instanceof, pe care nu l-am
abordat în capitolul 3. Rețineți că instanceof, ca și typeof, este scris cu minuscule, nu cu litere de
cămilă.
Să interogăm JavaScript cu privire la tipurile de care mintChocolateChunk este o instanță cu
instanceof. Rețineți că cel de-al doilea operand la instanceof este identificatorul constructorului, deci
nu adăugați operatorul (). De asemenea, rețineți că, la fel ca orice obiect, mintChocolateChunk este o
instanță a constructorului Object(), de la care moștenește metode precum valueOf() și toString().
Astfel, după cum arată figura 5-8, mintChocolateChunk este o instanță a patru tipuri, ceea ce înseamnă
că moștenește membri de la patru constructori.
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];
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 mintChocolateChunk = new MintChocolateChunk();

mintChocolateChunk instanceof MintChocolateChunk;


// adevărat
mintChocolateChunk instanceof Chocolate;
// adevărat
mintChocolateChunk instanceof Coffee;
// fals
mintChocolateChunk instanceof VanillaBean;
// adevărat
mintChocolateChunk instanceof Object;
// adevărat
157
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.

Membrii moșteniți sunt partajați, nu copiate


Este important să rețineți că membrii moșteniți sunt partajați între instanțe. Prin urmare, dacă vă
amintiți din capitolul 3, === va returna true pentru un membru pe care două instanțe îl împart, chiar și
un membru care este un obiect, o matrice sau o funcție. Pentru a ilustra acest aspect, să înlănțuim
comparațiile && pentru a confirma că instanțele VanillaBean, Coffee, Chocolate și
MintChocolateChunk împart matricea heavyCream, [1, "cup", "Organic Valley"], verificând munca
noastră cu figura 5-9:
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"];
158
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

};
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();

vanilla.heavyCream === coffee.heavyCream & &


vanilla.heavyCream === chocolate.heavyCream &&
vanilla.heavyCream === mintChocolateChunk.heavyCream &&
mintChocolateChunk.heavyCream === coffee.heavyCream
&& coffee.heavyCream === chocolate.heavyCream &&
mintChocolateChunk.heavyCream === chocolate.heavyCream;
// adevărat

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!

Modificarea instanțelor noi și anterioare ale unui tip


Acum, dacă modificăm, adăugăm sau eliminăm un membru din MintChocolateChunk.prototype,
Chocolate.prototype, Coffee.prototype sau VanillaBean.prototype, modificarea va fi evidentă în orice
instanță, atât nouă, cât și veche, care moștenește membrul. Astfel, de exemplu, dacă modificăm
MintChocolateChunk.prototype.bittersweet[2] de la "Callebaut" la "Lindt" după ce am creat
mintChocolateChunk, membrul său bittersweet reflectă modificarea, după cum arată figura 5-10:
var VanillaBean = function(vanilie, scorțișoară) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; cinnamon &&
(this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
heavyCream: [1, " cup", "Organic Valley"],
160
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

Jumătate Jumătate: [2, "ceaș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];
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 mintChocolateChunk = new MintChocolateChunk();
console.dir(mintChocolateChunk);

MintChocolateChunk.prototype.bittersweet[2] = "Lindt";
console.dir(mintChocolateChunk);

Figura 5-10. Schimbarea MintChocolateChunk.prototype.bittersweet[2] de la "Callebaut" la "Lindt" după


crearea mintChocolateChunk modifică membrul său bittersweet.
161
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

Acum, să adăugăm patru Heath Bar-uri tocate în prototipul VanillaBean.prototype pentru a


schimba vanilia în Ben & Jerry's Vanilla Heath Bar Crunch și cafeaua în Ben & Jerry' Coffee Heath Bar
Crunch. Cu toate acestea, după cum arată figura 5-11, făcând acest lucru, ne trezim și cu Chocolate Heath
Bar Crunch și Mint Chocolate Chip Heath Bar Crunch, deoarece tot ceea ce moștenește din VanillaBean
primește membrul heathBars!
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];
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();
var coffee = new Coffee();
var chocolate = new Chocolate();
var mintChocolateChunk = new MintChocolateChunk();

VanillaBean.prototype.heathBars = [4, "Batoane Heath, tăiate în bucăți"]; console.dir(vanilla);


console.dir(cafea);
console.dir(ciocolată);
console.dir(mentăCocolatăCluj);
162
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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.

Împărtășirea unui prototip, dar renunțarea la lanț


Un dezavantaj al înlănțuirii prototipurilor este că membrii proprii se transformă în membri moșteniți. De
exemplu, legarea MintChocolateChunk.prototype la Chocolate.prototype prin invocarea Chocolate() a pus
membrii cacao și dulce-amărui în MintChocolateChunk.prototype, așa că a trebuit să ștergem
MintChocolateChunk.prototype.cocoa și să modificăm MintChocolateChunk.prototype.bittersweet[0].
O modalitate de a evita acest inconvenient ar fi ca doi constructori să împartă un obiect prototip,
renunțând astfel la legătura în lanț. Să ne uităm la un exemplu: pentru a face înghețată de afine,
căpșuni, mango sau zmeură, în mod obișnuit voi face piure una dintre cele două cupe de fructe proaspete.
Apoi, tai jumătatea și jumătatea de la 2 cupe la 1 și gălbenușurile de la 6 la 3. Având în vedere acest
lucru, putem avea constructori pentru înghețata de afine și de căpșuni care să împartă un obiect
prototip părinte, așa cum se arată în continuare.
Rețineți că fraises des bois sunt mai mici, mai dulci și mai aromate decât căpșunile tradiționale.
Creați o instanță de Blueberry() și Strawberry(), verificând munca dvs. cu ajutorul figurii 5-12.
163
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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ă"],
gălbenușuri: [3],
vanilie: [1, "bean", "Madagascar Bourbon"]
};

var Blueberry = function(blueberry, lemon) {


this.blueberry = [ 2, "cup", blueberry ? blueberry : "Maine wild blueberries"];
this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
};
Blueberry.prototype = Berry.prototype;

var Strawberry = function(strawberry) {


this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"]];
};
Strawberry.prototype = Berry.prototype; var
blueberry = new Blueberry();
var strawberry = new Strawberry();
console.dir(blueberry);
console.dir(strawberry);

Figura 5-12. Nu există nicio legătură în lanț de prototipuri între Strawberry.prototype și Blueberry.prototype.

Astfel, renunțarea la legătura în lanț a prototipului a împiedicat eliminarea membrilor


Strawberry.prototype.blueberry și Strawberry.prototype.freshLemonJuice, așa cum se întâmplă în
exemplul următor și în figura 5-13, unde rețeta de căpșuni se bazează pe rețeta de afine:
var Blueberry = function(blueberry, lemon) {
this.blueberry = [ 2, "cup", blueberry ? blueberry : "Maine wild blueberries"];
this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
164
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

};
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);

Figura 5-13. Trebuie să ștergeți Strawberry.prototype.blueberry și


Căpșuni.prototype.freshLemonJuice membri

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"]
};

var Blueberry = function(blueberry, lemon) {


this.blueberry = [2, "cup", blueberry ? blueberry : "Maine wild blueberries"];
this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
};
Blueberry.prototype = Berry.prototype;

var Strawberry = function(strawberry) {


this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"]];
};
Strawberry.prototype = Berry.prototype; var
blueberry = new Blueberry();
var strawberry = new Strawberry();
Blueberry.prototype.cinnamon = [1, " stick", "Saigon"];
console.dir(afine);
console.dir(căpșuni);

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.

Adăugarea unei verigi de lanț goale


Ambele modele anterioare au un dezavantaj. Încatenarea prototipurilor transformă membrii proprii în
membri moșteniți, adăugându-i pe aceștia la prototipul constructorului copil. Renunțarea la lanțul de
prototipuri rezolvă această problemă, dar ne împiedică efectiv să adăugăm, să ștergem sau să
modificăm membrii prototipului.
Hmm. Ar exista o modalitate prin care un constructor copil să moștenească membrii din prototipul unui
constructor părinte, dar să aibă propriul prototip gol, în care să adauge membrii moșteniți?
Da, dar va trebui să fii cu zece degete de la picioare înăuntru pentru câteva momente pentru a o
obține. Mai întâi vom crea o funcție de ajutor numită extend care primește doi parametri:
166
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

• child va fi constructorul pe care dorim să-l avem ca prototip gol.

• parent va fi un constructor de la care dorim ca copilul să moștenească membrii.


var extend = function (child, parent) {
};
Acum vom crea un constructor numit Proxy() cu un bloc de cod gol. În acest fel, atunci când vom
invoca Proxy() cu new, acesta va returna un obiect gol.
var extend = function (child, parent) {
var Proxy = function () {};
};
Acum, la fel cum am făcut pentru Strawberry.prototype și Blueberry.prototype, vom face ca
Proxy.prototype să se refere la parent.prototype. Altfel spus, nu există un lanț de prototipuri între
Proxy.prototype și parent.prototype. Mai degrabă, acestea sunt doar două nume pentru același
obiect.
var extend = function (child, parent) { var
Proxy = function () {}; Proxy.prototype =
parent.prototype;
};
Acum, așa cum am legat MintChocolateChunk.prototype de Chocolate.prototype cu expresia de
atribuire MintChocolateChunk.prototype = new Chocolate(), vom lega child.prototype de
parent.prototype c u expresia de atribuire child.prototype = new Proxy(). Rețineți că, în ceea ce privește
crearea unei legături de la child.prototype la parent.prototype, expresiile new parent() și new Proxy() ar
fi echivalente, deoarece parent.prototype și Proxy.prototype se referă la același obiect.
Cu toate acestea, deoarece Proxy() are un corp gol, acesta returnează un obiect gol legat de
parent.prototype. Acest obiect gol este atribuit la child.prototype.
var extend = function (child, parent) { var
Proxy = function () {}; Proxy.prototype =
parent.prototype; child.prototype =
new Proxy();
};
Acum este ineficient să creăm Proxy() de fiecare dată când invocăm extend(), așa că, prin intermediul unei
metode de auto-invocare
vom salva Proxy() într-o închidere. Deocamdată, dați din cap în cunoștință de cauză; vom aborda funcțiile
auto-invocate și închiderile în Capitolul 6.
var extend = (function () {
var Proxy = function () {};
return function (child, parent) {
Proxy.prototype = parent.prototype;
child.prototype = new Proxy();
}
}());
Încatenarea prototipurilor sau utilizarea extend() suprascrie prototipul implicit și membrul
constructor al acestuia, care se referă pur și simplu la funcția care a construit obiectul, așa că haideți
să adăugăm un membru constructor la child.prototype:
var extend = (function () { var
Proxy = function () {};
return function (child, parent) {
Proxy.prototype = parent.prototype;
167
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

child.prototype = new Proxy();


child.prototype.constructor = child;
}
}());
Dezavantajul acestui procedeu este că, în timp ce membrul constructorului implicit nu ar fi
enumerat într-o buclă for in, cel pe care îl adăugați manual ar fi enumerat. Din acest motiv, este
posibil să renunțați la adăugarea unui membru constructor, dar l-am adăugat aici pentru a putea spune
că ați văzut unul în acțiune. Rețineți că JavaScript nu are nevoie de membrul constructor pentru ca
lanțul de prototipuri, operatorul instanceof sau orice altă caracteristică să funcționeze.
O practică obișnuită este adăugarea unui membru static la child (nu la child.prototype) care face
trimitere la parent.prototype. Acest lucru oferă o modalitate de interogare a părintelui unui tip (cunoscut
sub numele de superclasa sa). Acest lucru nu este ceva ce veți face ca începător. Cu toate acestea, veți
vedea acest lucru prin preajmă, așa că haideți să adăugăm un donator numit donor:
var extend = (function () { var
Proxy = function () {};
return function (child, parent) {
Proxy.prototype = parent.prototype;
child.prototype = new Proxy();
child.prototype.constructor = child;
child.donor = parent.prototype;
}
}());
Acum că am scris extend(), să îl folosim pentru a crea un prototip gol pentru un constructor
CherryGarcia() care este legat de Strawberry.prototype. În acest fel, instanțele lui CherryGarcia vor
moșteni membrii din Strawberry.prototype, dar nu și membrul strawberry creat în corpul lui
Strawberry(). Cireșele sunt mai dulci decât căpșunile, așa că haideți să suprascriem
Strawberry.prototype.sugar prin adăugarea unui membru sugar la CherryGarcia.prototype.
Acum, momentul adevărului. Creați instanțe ale Strawberry() și CherryGarcia(), trecându-le
metodei console.dir() din Firebug. Apoi, verificați-vă munca cu ajutorul figurii 5-15.
Cherry Garcia. Mmmh.
var extend = (function () { var
Proxy = function () {};
return function (child, parent) {
Proxy.prototype = parent.prototype;
child.prototype = new Proxy();
child.prototype.constructor = child;
child.donor = parent.prototype;
}
}());
var Strawberry = function(strawberry) {
this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"]];
};
Strawberry.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 CherryGarcia = function(cherry, bittersweet) {
168
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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);

Figura 5-15. cherryGarcia moștenește membri numai din Strawberry.prototype.

Furtul unui constructor


Cu extend(), putem conecta prototipul unui constructor la prototipul altui constructor. Dar ce se
întâmplă dacă dorim să duplicăm complet un constructor? Cu alte cuvinte, să spunem că dorim să
moștenim membrii din prototipul său și să împrumutăm toți ceilalți membri care nu se află în prototip.
Am face primul lucru cu extend(), iar cel de-al doilea invocând metoda apply() a constructorului părinte
din interiorul constructorului copil, trecându-i acestuia și o matrice de parametri.
De exemplu, dacă am dori ca CherryGarcia() să fie o clonă a lui Cherry() care adaugă bucățele de
ciocolată dulce-amăruie Callebaut, am lega CherryGarcia.prototype de Cherry.prototype cu extend().
Apoi, invocăm Cherry.apply() din interiorul CherryGarcia(), trecând acest parametru și parametrul
cherry, ca în exemplul următor și în figura 5-16:
169
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

var extend = (function () { var


Proxy = function () {};
return function (child, parent) {
Proxy.prototype = parent.prototype;
child.prototype = new Proxy();
child.prototype.constructor = child;
child.donor = parent.prototype;
}
}());
var Cherry = function(cherry) {
this.cherries = [2, "ceașcă, fără sâmburi și tăiate în două", cherry ? cherry : "Bing"];
};
Cherry.prototype = {
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],
vanilie: [1, "bean", "Madagascar Bourbon"]
};
var CherryGarcia = function(cherry, bittersweet) {
Cherry.apply(this, [cherry]);
this.bittersweet = [1, "ceașcă, tocat grosier", bittersweet ? bittersweet : "Callebaut"];
};
extend(CherryGarcia, Cherry);
var cherry = new Cherry();
var cherryGarcia = new CherryGarcia();
console.dir(cherry);
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"],

Jumătate Jumătate: [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ă, tocat grosier"];
chunkyMonkey.bittersweet = [1, "ceașcă, tocat grosier", "Callebaut"]; console.dir(banana);
consola.dir(chunkyMonkey);

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"], jumate: [2, "ceașcă", "Organic

Valley"],

zahăr: [5/8, "ceașcă"],


gălbenușuri: [6],
cacao: [3/8, "ceașcă", "Callebaut, proces olandez"],
vanilie: [1, "boabă", "Madagascar Bourbon"]
};
var newYorkSuperFudgeChunk = emulate(chocolate, {
pecans: [1/4, "ceașcă, tocate grosier"],

nuci: [1/4, "cană, tocate grosier"],


migdale: [1/4, "cană, tocate grosier"],
albCocolată: [1/3, "ceașcă, mărunțită grosier", "Callebaut"], ciocolată
dulce-amăruie: [1/3, "ceașcă, tocat grosier", "Callebaut"]]
});
console.dir(ciocolată);
console.dir(newYorkSuperFudgeChunk);
173
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

Figura 5-18. Adăugarea de membri în moștenirea prototipală

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

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;
};
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 5-19. Efectuarea unei copii profunde a 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 merge = function (mixins, donee) {


var i, j, donee = donee || {};
for (i = 0, j = mixins.length; i < j; i ++) {
cloneMembers(mixins[i], donee);
}
retur donatar;
};
Acum să creăm câteva mix-uri. Pe parcursul acestui capitol, am folosit o bază de cremă dulce franțuzească;
alte tipuri de baze de înghețată includ Philadelphia, c a r e nu conține gălbenușuri, și gelato italiană, care
are un raport gălbenușuri/cremă mai mare decât cea franceză și, prin urmare, oferă o î n g h e ț a t ă mai
densă. În plus față de aceste baze, vom crea câteva mixuri pentru arome.
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 merge = function (mixins, donee) {
var i, j, donee = donee || {};
for (i = 0, j = mixins.length; i < j; i ++) {
cloneMembers(mixins[i], donee);
}
retur donatar;
};
176
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

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"],

Jumătate Jumătate: [1, "ceașcă",


"Organic Valley"], zahăr: [5/8, "ceașcă"]
};
var gelato = {
Jumătate Jumătate: [3, "ceașcă",
"Organic Valley"], zahăr: [5/8,
"ceașcă"],
gălbenușuri: [6]
};
var vanilla = {
vanilie: [1, "bean", "Madagascar Bourbon"]
};
var heathBar = {
heathBars: [4, "batoane, tocate grosier"]]
};
var coffee = {
cafea: [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"]
};
După ce am făcut acest lucru, vom crea Cafeaua în stil italian Heath Bar Crunch, Cafeaua în stil Philadelphia
și
French-style Vanilla Heath Bar Crunch, verificând munca noastră cu figura 5-20:
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;
};
var merge = function (mixins, donee) {
var i, j, donee = donee || {};
for (i = 0, j = mixins.length; i < j; i ++) {
cloneMembers(mixins[i], donee);
}
retur donatar;
};
var french = {
cremă de smântână: [1, "ceașcă", "Organic
Valley"], jumate: [2, "ceașcă", "Organic
Valley"],
177
CAPITOLUL 5 ■ MOȘTENIREA MEMBRILOR

zahăr: [5/8, "ceașcă"],


gălbenușuri: [6]
};
var philly = {
cremă: [2, "ceașcă", "Organic Valley"],
jumate: [1, " ceașcă", "Organic Valley"],
zahăr: [5/8, "ceașcă"]
};
var gelato = {
Jumătate Jumătate: [3, "ceașcă",
"Organic Valley"], zahăr: [5/8,
"ceașcă"],
gălbenușuri: [6]
};
var vanilla = {
vanilie: [1, "bean", "Madagascar Bourbon"]
};
var heathBar = {
heathBars: [4, "batoane, tocate grosier"]
};
var coffee = {
cafea: [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"]
};

var coffeeHeathBarCrunch = merge([gelato, vanilla, coffee, heathBar]);


console.dir(coffeeHeathBarCrunch);
var coffee = merge([philly, vanilla, coffee]); console.dir(coffee);
var vanillaHeathBarCrunch = merge([french, vanilla, heathBar]);
console.dir(vanillaHeathBarCrunch);

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

• Se separă gălbenușurile de ou în bolul de mixare.

• Se taie boabele de vanilie pe lungime cu un cuțit de bucătărie.

• Răzuiți semințele mici și păstoase într-o cratiță. Apoi adăugați jumătățile


goale de păstaie - cea mai mare parte a aromei de vanilie provine din păstaie.

• Adăugați 2 căni de jumătate și jumătate, 1/2 cană de frișcă și 5/8 cană de


zahăr în cratița în care se află semințele de vanilie și jumătățile de păstaie.

181
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

• Adăugați restul de 1/2 ceașcă de frișcă la cele 6 gălbenușuri de ou și bateți


energic până la omogenizare.

• Amestecând frecvent cu lingura de lemn, puneți cratița la foc mediu timp de 4


minute sau doar până când lichidul începe să se onduleze, dar nu fierbe.

• Se temperează gălbenușurile de ou adăugând treptat 1/4 din lichidul fierbinte


din cratiță, în timp ce se bate continuu. Temperarea împiedică gălbenușurile să se
închege, ceea ce ar strica crema.

• Se toarnă amestecul de gălbenușuri temperat în cratiță, în timp ce se bate constant.

• Amestecând constant cu lingura de lemn, puneți cratița la foc mediu timp de 4


minute sau până când crema se îngroașă la aproximativ 170 F - nu fierbeți.

• 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ă.

• Amestecând din când în când, se răcește crema plasând castronul într-un


castron mai mare umplut pe jumătate cu apă rece.

• 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.

• La final, se bate crema răcită în mașina de înghețată și apoi se dă la congelator


câteva ore sau până se întărește.

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

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/4 ceașcă de boabe Starbucks Espresso, proaspăt și grosier măcinate
1 păstaie de vanilie Bourbon din Madagascar

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
3/8 cană de cacao Callebaut
1 păstaie de vanilie Bourbon din Madagascar
În JavaScript, dacă vă găsiți scriind o anumită secvență de declarații la nesfârșit, care diferă doar
puțin, așa cum s e î n t â m p l ă cu pașii din rețetele noastre pentru înghețată de vanilie, cafea și ciocolată,
atunci ar trebui să

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ă.

Funcțiile sunt valori


Funcțiile de primă clasă pot fi exprimate cu literali. În mod obișnuit, acestea sunt nenumite sau
anonime. O funcție se invocă prin intermediul variabilei, membrului sau elementului în care a fost
salvată. De exemplu, în capitolul 5, am salvat o expresie de funcție fără nume într-o variabilă numită
WildMaineBlueberry și am invocat funcția anonimă prin intermediul numelui variabilei,
WildMaineBlueberry:
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 = new WildMaineBlueberry();
console.dir(wildMaineBlueberry);
Valorile funcțiilor numite pot fi create cu ajutorul unei declarații de funcție. La rândul său, puteți
invoca funcția prin numele acesteia. Astfel, în loc să salvăm o expresie de funcție într-o variabilă
numită WildMaineBlueberry, am putea crea o funcție numită WildMaineBlueberry astfel:
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"]
};
var wildMaineBlueberry = new WildMaineBlueberry();
console.dir(wildMaineBlueberry);

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).

Încărcare anticipată condiționată


O implicație a faptului că funcțiile sunt valori este că puteți alege condiționat una dintre cele două sau
mai multe valori pentru o funcție la încărcarea scriptului. Această tehnică, denumită încărcare
anticipată condiționată sau ramificare în timp de încărcare, este una la care vom recurge destul de des
în capitolele 9 și 10.
Încărcarea anticipată condiționată poate fi utilizată pentru a alege o valoare pentru o funcție în
funcție de caracteristicile ECMAScript sau DOM pe care le implementează interpretul JavaScript al
browserului. Acest lucru înseamnă că setăm valoarea funcției pentru a utiliza o anumită caracteristică
dacă aceasta este disponibilă sau scriem acea caracteristică în valoarea funcției dacă nu este disponibilă.
De exemplu, ECMAScript 5 definește următoarele 12 metode noi. Rețineți că acestea sunt metode
statice, ceea ce înseamnă că sunt salvate în Object, nu în Object.prototype.
Object.create()
Object.defineProperty()
Object.defineProperties()
Object.getOwnPropertyDescriptor(
) Object.keys()
Object.getOwnPropertyNames()
Object.preventExtensions()
Object.isExtensible()
Object.seal()
Obiect.isSealed()
Obiect.freeze()
Obiect.isFrozen()
Obiect.isFrozen()
În acest moment, niciun browser nu acceptă aceste metode, dar interpreții JavaScript din Explorer
9 și Firefox 4 vor face acest lucru. Cu toate acestea, aceste metode vor îmbunătăți foarte mult
caracteristicile de moștenire ale JavaScript. Așadar, dacă sunt disponibile, am dori să le integrăm în
funcțiile pe care le-am scris în capitolul 5.
O modalitate de a face acest lucru este încărcarea anticipată condiționată. Partea "condițională"
înseamnă o instrucțiune condițională if sau o expresie condițională ?: care verifică dacă o anumită
caracteristică este disponibilă, în timp ce partea de "încărcare anticipată" înseamnă că detectarea
caracteristicilor se face la încărcarea scriptului, adică în avans.
Așadar, cu jargonul în mână, să rescriem extend() din capitolul 5, astfel încât Explorer 9 și
Firefox 4 să poată folosi următoarele metode ECMAScript 5, pe care le voi explica pe măsură ce vă
voi arăta cum să le implementați:
Object.create()
Object.defineProperty()
Object.defineProperties()
Primul lucru pe care dorim să îl facem este să definim metode nu foarte diferite de cele din
ECMAScript 5 pentru versiunile actuale ale Explorer, Firefox, Safari și Opera; cu alte cuvinte, vom scrie
propriile noastre versiuni de create(), defineProperty() și defineProperties() pentru ca browserele să le
folosească în cazul în care nu acceptă

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.

Înainte de ECMAScript 5, orice membru pe care îl adăugați la un obiect era inscriptibil,


enumerabil și ștergibil. Pentru majoritatea membrilor, asta este ceea ce vă doriți. Așadar,
Object.defineProperty() nu este un înlocuitor pentru adăugarea de membri cu operatorii ., [] și =. Mai
degrabă, este doar pentru a face lucruri precum asigurarea faptului că membrii precum constructor
(pentru un obiect prototip) nu apar într-o buclă for in.
Pentru a face acest lucru, trebuie să transmiteți Object.defineProperty() trei parametri.

• În primul rând, un obiect la care să adăugați un membru sau care conține


un membru pe care doriți să-l modificați.

• Î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

• În al treilea rând, un descriptor de date sau un obiect descriptor de accesori.


Descriptorii de date pot conține următorii patru membri. În mod minim, un
descriptor de date trebuie să conțină o valoare sau un membru inscriptibil.

• value conține valoarea membrului, care are valoarea implicită nedefinită.

• writable conține un boolean care indică dacă valoarea membrului este


inscriptibilă. true înseamnă că este, iar false, care este valoarea implicită,
înseamnă că nu este. Astfel, false înseamnă că nu puteți atribui o nouă
valoare unui membru cu ajutorul operatorului =.

• configurable conține un boolean care indică dacă un membru poate


fi șters și dacă atributele descriptorului său pot fi scrise, cu excepția
lui writable, care este gravat în piatră. false, valoarea implicită,
înseamnă că un membru nu poate fi șters și că atributele sale
configurabile și enumerabile nu pot fi modificate. true înseamnă
invers.

• enumerable conține un boolean care indică dacă membrul va fi enumerat


într-o buclă for in. true înseamnă că va fi enumerat, iar false, valoarea
implicită, înseamnă că nu va fi enumerat. Astfel, enumerable oferă o
modalitate de a se asigura că un membru, cum ar fi constructorul, nu apare
într-o buclă for in.
Rețineți că atributele de descriptor inscriptibil, configurabil și enumerabil au valoarea implicită
false. Pe de altă parte, pentru un membru creat sau modificat în mod tradițional cu ajutorul
operatorului =, writable, configurable și enumerable au valoarea implicită true. Acesta este un indiciu
pentru a continua să folosiți = pentru majoritatea operațiilor de atribuire.
Rețineți că, dacă adăugați un membru cu =, îi puteți modifica mai târziu caracteristicile sale de scriere,
configurare și
atribute enumerabile cu Object.defineProperty().

■ 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.

În cazul browserelor care nu implementează Object.defineProperty(), vom dori pur și simplu să


atribuim membrul de valoare al descriptorului cu operatorul = și să nu ținem cont de membrii
inscriptibili, configurabili și enumerabili. Rețineți că, în cazul în care descriptorul are doar un
membru inscriptibil, descriptor.value se evaluează la undefined.
if (Object.defineProperty === undefined) {
Object.defineProperty = function (obj, name, descriptor) {
obj[name] = descriptor.value;
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function () {
};
}
if (Object.create === undefined) {
Object.create = function () {
};
}

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.

• Primul este același cu Object.defineProperty()- obiectul la care se adaugă sau se


modifică membrii.

• 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:

if (Object.defineProperty === undefined) {


Object.defineProperty = function (obj, name, descriptor) {
obj[name] = descriptor.value;
};
}
if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, d e s c r i p t o r s )
{ for (descriptor in descriptors) {
if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
}
}
};
}
if (Object.create === undefined) {
Object.create = function () {
};
}

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).

• Primul este obiectul de la care se moștenesc membrii.

• 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;
};
}

Utilizarea noilor funcții


Acum, să invocăm toString() pe Object.defineProperty, Object.defineProperties și Object.create și
apoi să transmitem șirul de caractere metodei console.log() a Firebug. Dacă executați Firefox 3,
JavaScript va imprima funcțiile noastre pre-ECMAScript 5 în panoul din stânga al Firebug, ca în figura 6-
1. Pe de altă parte, dacă executați Firefox 4, JavaScript va imprima funcțiile native ECMAScript 5.
Rețineți că funcțiile native sunt scrise într-un limbaj compilat, cum ar fi C++, așa că, în loc să tipăriți
gogomănie compilată pentru corpul unei funcții native, JavaScript tipărește pur și simplu [cod nativ]:
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;
}
}
};
}
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;
};
189
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

}
console.log(Object.defineProperty.toString());
console.log(Object.defineProperties.toString());
console.log(Object.create.toString());

Figura 6-1. Firefox 3 optează pentru funcțiile noastre pre-ECMAScript 5.

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
});
};

var Strawberry = function(strawberry) {


this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"]];
};
Strawberry.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 CherryGarcia = function(cherry, bittersweet) {
this.cherries = [ 2, " ceașcă, fără sâmburi și înjumătățite", cherry ? cherry : "Bing"];
this.bittersweet = [1, "ceașcă, mărunțită grosier", bittersweet ? bittersweet : "Callebaut"]];
};
extend(CherryGarcia, Strawberry, {
sugar: {
valoare: [9/16, "cup"],
193
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

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

return new Proxy();


};
}
};
Din nefericire, în momentul de față, clone() s-ar redefini pur și simplu la prima apelare. Adică, ar
returna un obiect nedefinit în loc de un obiect gol care moștenește membri de la donator.
Hmmm. Ce
să fac?
Știu. În urma declarației if else, vom trece donatorul de la vechea clone() la noua clone():
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;
return new Proxy();
};
}
clonă (donator);
};
Dar aceasta va returna în continuare nedefinit prima dată când vom apela clone(), deoarece o funcție care
nu este
să returneze în mod explicit o valoare cu o instrucțiune return va returna implicit o valoare nedefinită.
Ținând cont de acest lucru, cum am putea repara încărcătorul nostru leneș?
Da, vechiul clone() ar trebui să returneze în mod explicit valoarea trecerii parametrului său donor
către noul clone(). Rețineți că, dacă redefiniți o funcție care nu returnează în mod explicit o valoare,
cum ar fi funcțiile thwart() sau burst() pe care le vom scrie în capitolul 9, atunci puteți omite cuvântul
cheie return.
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;
return new Proxy();
};
}
return clone(donor);
};
Acum să creăm niște Ben & Jerry's Chunky Monkey cu clona noastră de încărcare leneșă(), așa cum am făcut
în
Capitolul 5, înainte de a verifica munca noastră cu ajutorul figurii 6-3. Dacă executați Firefox 4, atunci
chunkyMonkey este transformat de Object.create(). Dar dacă executați Firefox 3, atunci chunkyMonkey
este transformat de Proxy(). Este delicioasă în ambele cazuri, dar veți avea mai repede o lingură dacă
Object.create() este cel care face mișcarea.
var clone = function (donor) {
if (Object.create !== undefined) {
clone = function (donor) {

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

if (typeof d o n o r [ m] === "object" && donor[m] !== null) {


donee[m] = typeof donor[m].pop === "function" ? [] : {};
arguments.callee(donor[m], donee[m]);
} 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.coffee = [1/4, "ceașcă, măcinată grosier", "Starbucks Espresso"];

Împrumut de metode cu apply() sau call()


În măsura în care funcțiile sunt valori de tip valoare de obiect, ele moștenesc membrii din
Object.prototype și Function.prototype prin intermediul unui lanț de prototipuri, în același mod în
care obiectul nostru cherryGarcia moștenește membrii din Object.prototype, Strawberry.prototype și
CherryGarcia. Două metode moștenite de la Function.prototype, apply() și call(), oferă o modalitate de
a împrumuta valorile funcțiilor. Aceste funcții înseamnă că puteți defini o funcție într-un obiect și o
puteți utiliza în alt obiect ca și cum ar fi moștenită, fără a fi nevoie să moșteniți întregul obiect. În
ambele cazuri, treceți obiectul care moștenește funcția ca prim parametru (care devine "this" în funcția
moștenită), urmat de oricare dintre aceste două parametri:

• Argumentele funcției sub forma unei serii de valori separate prin virgulă, în
cazul lui call()

• Argumentele funcției sub formă de tablou, în cazul aplicației()


ECMAScript 5 definește o metodă Array.isArray() pentru a verifica dacă o valoare este sau nu o
matrice. Aceasta este o adăugare foarte necesară pentru JavaScript, în condițiile în care typeof
returnează "object" pentru un obiect, un array sau null, iar operatorul instanceof nu funcționează cu
cadrele în unele versiuni ale Explorer. Să vedem cum pot fi de ajutor call() și apply().

Suprascrierea lui toString()


Așadar, pentru Explorer 9, Firefox 4 și alte browsere care cunosc ECMAScript 5, dorim să verificăm dacă
există o matrice cu Array.isArray(), mai degrabă decât să testăm o metodă de matrice precum pop() sau
slice(), așa cum am făcut în cloneMembers(). La urma urmei, nu există niciun motiv pentru care un
obiect sau o funcție să nu aibă o metodă numită pop() sau slice().
Pentru a face acest lucru, am dori să scriem un încărcător anticipat condițional pentru
Array.isArray(). Deci, la fel cum am făcut pentru Object.create(). Partea complicată este să scriem
ceva nu foarte diferit de Array.isArray() pentru Explorer 8, Firefox, 3 și alți manechine ECMAScript
5. Pentru a face acest lucru, vom lucra cu funcția toString() pentru a extrage informații despre
obiectul în cauză.
201
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

Dacă se invocă toString() pe un obiect care nu suprascrie Object.prototype.toString() cu propria


metodă toString(), se va returna "[obiect Obiect]". De exemplu, chiar dacă am transformat
wildMaineBlueberry cu constructorul WildMaineBlueberry() din capitolul 5, după cum arată figura 6-
5, invocarea wildMaineBlueberry.toString() returnează "[object Object Object]" în măsura în care nu
am suprascris Object.prototype.toString():
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 = new WildMaineBlueberry("Dole frozen wild b l u e b e r r i e s ", "Tahitian");
wildMaineBlueberry.toString();
// "[obiect Obiect]"

Figura 6-5. Invocarea metodei toString() pe un obiect care nu suprascrie Object.prototype.toString()


cu propria metodă toString(), va returna "[obiect Obiect]".

Cu toate acestea, dacă adăugăm o metodă toString() la WildMaineBlueberry.prototype, care va suprascrie


metoda
Object.prototype.toString(), după cum se arată în exemplul următor și în figura 6-6:
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],
202
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

freshLemonJuice: [2, "tsp"],


toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild b l u e b e r r i e s ", "Tahitian");
wildMaineBlueberry.toString();
// "[obiect WildMaineBlueberry]"

Figura 6-6. WildMaineBlueberry.prototype.toString() suprascrie Object.prototype.toString().

Constructorii nativi JavaScript definiți de ECMAScript sau DOM suprascriu întotdeauna


Object.prototype.toString(). De exemplu, dacă apelăm toString() pe array-ul din
wildMaineBlueberry.heavyCream, JavaScript lipește elementele împreună cu virgule în loc să
returneze "[object Array]", ceea ce Object.prototype.toString() returnează pentru un array atunci când
nu este suprascris. În mod similar, dacă apelăm toString() pe funcția constructor WildMaineBlueberry,
JavaScript returnează definiția acesteia sub forma unui șir de caractere. Încercați să le faceți pe
amândouă, verificând munca dvs. cu ajutorul figurii 6-7.
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"],
toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");
wildMaineBlueberry.heavyCream.toString();
// "1, ceașcă, Organic Valley"
WildMaineBlueberry.toString();
// "function (afine, vanilie) { this.afine = [2, "cup", afine ? afine
: "fresh wild Maine blueberries"]]; this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar
Bourbon"]; }"
203
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

Figura 6-7. Constructorii nativi JavaScript definiți de ECMAScript sau DOM suprascriu întotdeauna
Object.prototype.toString().

Testarea pentru o matrice


Pentru a testa dacă există un array, cum ar fi Array.isArray() în ECMAScript 5, cum ar fi Explorer 8,
trebuie să ocolim orice metodă toString() care suprascrie Object.prototype.toString() pentru a evita
șirul lipicios pe c a r e JavaScript ni-l arată atunci când apelăm toString() pe un array. După ce am
făcut acest lucru, vom returna true dacă Object.prototype.toString() returnează "[object Array]" și
false în caz contrar.
ECMAScript definește câteva metode pentru valorile funcțiilor, apply() și call(), care oferă o
modalitate de a împrumuta o metodă cum ar fi Object.prototype.toString() și de a o utiliza ca și cum ar
fi moștenită (astfel încât să avem acces la ea peste orice metodă suprascrisă).
Primul parametru pentru apply() sau call() este un obiect pe care să îl legați de metoda pe care
o împrumutați. Dacă ați dori să invocați Object.prototype.toString() pe [1, "cup", "Organic
Valley"], adică să ocoliți Array.prototype.toString(), ați trece [1, "cup", "Organic Valley"] ca prim
parametru pentru apply() sau call(). Încercați ambele metode, verificând munca dvs. cu ajutorul
figurii 6-8. Rețineți că, pentru a economisi ceva dactilografiere, puteți înlocui Object.prototype cu un
literal de obiect gol, încadrat între paranteze.
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"],
toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");

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]"

Figura 6-8. Ocolirea lui Array.prototype.toString() pentru a verifica caracterul de matrice

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

toString: function () { return "[object WildMaineBlueberry]";}


};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");

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

Object.defineProperties = function (obj, d e s c r i p t o r s )


{ 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) {
var that = this;
};
Prin intermediul operatorului virgulă, definiți o altă variabilă numită ossify. Împrumutați apoi
metoda slice() de la un literal de array gol cu metoda call(), pe care slice() o moștenește din
Function.prototype ca orice altă funcție. Apoi vom invoca slice() pentru a salva orice argumente fără
nume transmise la bind() sub forma unui array. Astfel, ossify nu conține obj, ci doar orice argumente
suplimentare.
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) {

209
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

var that = this,


ossify = [].slice.call(arguments, 1);
};
Prin intermediul operatorului virgulă, vom defini o a treia variabilă cu ajutorul instrucțiunii var.
Aceasta se va numi fn și va conține valoarea funcției creată prin curtarea that și ossify.
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) {
var that = this,
ossify = [].slice.call(arguments, 1),
fn = function () {
return that.apply(this instanceof that ? this : obj,
ossify.concat([].slice.call(arguments, 0)));
};
};
Acum, în cazul în care acesta conține o funcție constructor, trebuie să ne asigurăm că fn are
același lanț de prototipuri. Pentru a face acest lucru, vom atribui lui fn.prototype valoarea de returnare
a trecerii lui that.prototype la Object.create(). Acest lucru asigură faptul că obiectele create de that și
fn moștenesc aceiași membri. În cele din urmă, dorim să returnăm valoarea funcției din fn, care este o
combinație a valorii funcției din that și a parametrilor din ossify.
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) {

210
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

var Proxy = function () {}},


child;
Proxy.prototype = parent;
child = new Proxy();
if (descriptors !== undefined) {
Object.defineProperties(child, descriptors);
}
întoarce copilul;
};
}
Function.prototype.bind = Function.prototype.bind ||
function (obj) {
var that = this,
ossify = [].slice.call(arguments, 1),
fn = function () {
return that.apply(this instanceof that ? this : obj,
ossify.concat([].slice.call(arguments, 0)));
};
fn.prototype = Object.create(that.prototype);
return fn;
};
Acum, momentul adevărului. Haideți să încercăm să curăm un constructor care să prepare înghețata
Wild Maine Blueberry cu afine și parametri de lămâie. În măsura în care vom prestabili acești parametri la
valorile lor tipice de iarnă - fără afine sălbatice proaspete acum - vom numi noul constructor
WinterWildMaineBlueberry. Apoi, din moment ce WinterWildMaineBlueberry acceptă doar un parametru
de vanilie - parametrii de afine și lămâie sunt prestabiliți - vom trece "Tahitian" pentru a alege o boabă de
vanilie blândă, verificând munca noastră cu figura 6-11:
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) {
var that = this,
ossify = [].slice.call(arguments, 1),

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;
};

var WildMaineBlueberry = function(afine, lămâie, vanilie) {


this.blueberries = [ 2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
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]
};
var WinterWildMaineBlueberry = WildMaineBlueberry.bind(null, "Dole frozen wild blueberries",
"Eureka");
var iceCream = new WinterWildMaineBlueberry("Tahitian");
console.dir(iceCream);

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

return new Proxy();


};
var churn = {};
churn._vanilla = function (vanilla) {
this.vanilla = [1, "bean", vanilla || "Madagascar Bourbon"];
return this;
};
churn._coffee = function (coffee) {
this.coffee = [1/4, "ceașcă, măcinată grosier", cafea || "Starbucks Espresso"];
return this;
};
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] return
this;
};
churn._coffee = function (coffee) {
this.coffee = [1/4, "ceașcă, măcinată grosier", cafea || "Starbucks Espresso"];
return this;
};
churn._print = function () {
var copy = {};
for (var m in this) {
this.hasOwnProperty(m) && (copy[m] = this[m]);
}
console.dir(copy);
};
var iceCream = clone(churn);
iceCream._french()._vanilla()._coffee()._print();
216
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

Figura 6-13. Încatenarea invocărilor de metode

Funcții de închidere și de returnare


Vanilia se numără printre cei mai puternici potențiatori de aromă, adică ne sporește capacitatea de a
gusta ciocolata, cafeaua, fructele, nucile și alte alimente. Mai mult, vanilia ne sporește percepția de
dulceață. Din acest motiv, cele mai multe arome de înghețată sunt înfrumusețări ale înghețatei de
vanilie.
Astfel, deși puteți face înghețată de ciocolată dintr-o bază de cremă dulce, adăugarea unei
boabe de vanilie sporește aroma de ciocolată a cacaoi. Pentru a face mai departe acest lucru, am
putea toca grosier o ceașcă, aproximativ 4 uncii, de ciocolată dulce-amăruie - aș recomanda Callebaut,
Ghirardelli sau Lindt - și să o topim în cremă.
Rețineți că ciocolata se obține prin zdrobirea boabelor de cacao și presarea lor într-o pastă
neîndulcită, denumită lichior de ciocolată, care este compusă din cacao și unt de cacao. Așadar, cacaua
se obține prin îndepărtarea untului de cacao și măcinarea pastei de cacao, acum fără grăsime, până la
obținerea unei pudre. Pentru a îndepărta o parte din aciditate și a spori aroma de ciocolată, cacao poate
fi dusă cu alcalii.
Pe de altă parte, ciocolata dulce-amăruie se obține prin adăugarea de unt de cacao suplimentar
la lichiorul de ciocolată pură și îndulcirea acestuia cu zahăr. Ciocolata amăruie dulce-amăruie fină
de la Callebaut, Lindt, Ghirardelli și alții conține 60 până la 70 la sută pastă de cacao pură, în timp
ce ciocolata amăruie ieftină conține de obicei doar 15 la sută cacao, minimul stabilit de FDA. Așadar, a
plăti un dolar sau doi în plus pentru o cantitate de patru sau cinci ori mai mare de cacao merită dacă
vă place înghețata de ciocolată.
217
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

Figura 6-14. Interogarea variabilelor locale salvate într-o închidere

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

Figura 6-15. Interogarea parametrilor numiți salvați într-o închidere

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

Figura 6-16. Exportarea funcției constructor dintr-o expresie de funcție auto-invocată

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-18. De asemenea, mutarea operatorului () în afara parantezelor funcționează bine.

Transmiterea unui obiect de configurare


Adesea, dacă aveți un număr de parametri opționali, cum ar fi în exemplele noastre de închidere, veți
dori să transmiteți un obiect de configurare mai degrabă decât parametri separați. În acest fel, evitați să
fiți nevoiți să treceți "" sau alți parametri cu valoare falsă înainte de cel pe care doriți să îl treceți în mod
explicit, ca în exemplul următor:
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("", "", "", "Tahitian");
console.dir(chocolateChocolate);
Iată cum putem remedia această problemă prin definirea unui parametru numit pref. După cum
arată figura 6-19, acest lucru ne împiedică să trecem șiruri goale pentru dulce-amărui și cacao pentru
a trece "Tahitian" pentru vanilie.
223
CAPITOLUL 6 ■ FUNCȚII ȘI MATRICE

var ChocolateChocolate = function () {


var _bittersweet = "Ghirardelli",
_cocoa = "Callebaut",
_vanilla = "Madagascar Bourbon";
return function (pref) {
pref || (pref = {});
this.bittersweet = [1, "cup", p r e f .bittersweet || _bittersweet];
this.cocoa = [3, "tbs", pref.cocoa || _cocoa];
this.vanilla = [1, "bean", pref.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({vanilie: "Tahitian"}); console.dir(chocolateChocolate);

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

S-ar putea să vă placă și