Lucrul cu obiecte în JavaScript: teorie și practică. Lucrul cu obiecte în javascript - Utilizarea funcțiilor - Teorie și practică

O vizită la o resursă web este un URI specific din bara de adrese a browserului. Vizitatorul specifică adresa paginii, iar aceasta este analizată de browser în elemente ale arborelui DOM - Document Object Model. Orice link de pe această pagină îi spune browserului să analizeze o altă pagină și să construiască un alt arbore de obiecte.

Browserul permite vizitatorului să meargă înapoi sau înainte printr-un lanț de pagini care au fost deja vizualizate în sesiunea curentă.

De fapt, acțiunile utilizatorului sunt mișcarea între sisteme de obiecte formate în procesul de vizitare a paginilor. Fiecare pagină este propriul arbore DOM și, în plus, obiectele JavaScript sunt obiecte ale sintaxei limbajului în sine și ale descrierilor utilizatorilor.

DOM: încărcare, actualizare și modificare

Există trei opțiuni principale care formează obiectele de pagină ale unei resurse web, atât la nivelul DOM, cât și al limbajului JavaScript însuși, care a realizat constructele de creare a variabilelor, și pe baza descrierilor făcute de dezvoltator:

  • incarcare - vizitatorul a ajuns la pagina site-ului;
  • actualizare - vizitator (buton browser sau Ctrl-F5);
  • modificarea unui element de pagină, de exemplu (AJAX, script, eveniment, ...).

Toate cele trei procese sunt fundamental diferite, dar distingerea caracteristicilor primelor două este deosebit de importantă. Este dificil să împiedici un vizitator să reîmprospăteze o pagină - acesta este un obicei „rău” ineradicabil al vizitatorilor de care dezvoltatorul ar trebui să-l țină cont.

Navigarea pe pagină și dincolo ar trebui să se regăsească exclusiv în funcționalitatea paginii în sine, și nu în istoricul browserului și funcțiile butoanelor sale. Multe site-uri declară această cerință importantă, dar vizitatorii o încalcă în mod tradițional.

Schimbarea paginii fără a o reîncărca la nivel de element individual (de exemplu, AJAX) este o soluție comună pentru paginile dinamice. De regulă, acesta este folosit pentru a naviga prin elementele paginii, pentru a schimba obiectele acesteia și pentru a gestiona dialogul cu vizitatorul.

Obiecte JavaScript fundamentale

JavaScript este bazat pe obiecte. Aproape toate variabilele de limbaj sunt obiecte. Dezvoltatorul își poate formula propriile descrieri de obiect folosind o varietate de opțiuni de sintaxă.

Orice lucru care nu este un „șir”, „număr”, adevărat, fals, nul sau nedefinit este un obiect. În cadrul sintaxei limbajului, aceasta poate fi ignorată, adică doar elementele DOM și descrierile proprii ale obiectelor JavaScript ca obiecte.Structura fundamentală a limbajului în majoritatea cazurilor nu are o semnificație practică semnificativă pentru dezvoltator.

De exemplu, funcțiile matematice sunt reprezentate de un obiect Math. Acest lucru este convenabil în cadrul conceptului de limbaj, dar pentru dezvoltator este pur și simplu o sintaxă convenabilă pentru utilizarea arsenalul necesar de operații matematice.

Este important să lucrați corect cu DOM-ul și să vă descrieți corect propriile obiecte. Sintaxa funcțiilor obiect JavaScript și expresiile pentru utilizarea lor este o formă de înregistrare a logicii algoritmului necesar.

Șiruri, tablouri și obiecte

Toate obiectele JavaScript se bazează pe regula: „proprietate” = „valoare” și pe conceptul de matrice asociativă. În forma sa cea mai simplă, un obiect JavaScript este o colecție de perechi proprietate = valoare. Cu toate acestea, „valoarea” poate să nu fie întotdeauna un număr, iar proprietatea nu este întotdeauna scrisă fără ghilimele.

Nu ar trebui să folosiți excesiv denumirea proprietăților. În mod ideal, numele de proprietăți conțin doar caractere latine, satisfac cerințele pentru denumirea variabilelor și nu sunt cuvinte cheie (inclusiv rezervate) ale limbii.

Nu se așteaptă o ordonare a proprietăților, dar atunci când se creează sau se inițialează un tablou asociativ, este destul de acceptabil să știi cum sunt aranjate elementele sale. Nu este recomandat să folosiți această circumstanță, dar este posibil să o aveți în vedere.

Inițializarea unei matrice de proprietăți înseamnă simultan:

  • crearea unei matrice;
  • crearea obiectului.

Într-un context specific de aplicare, puteți considera un obiect JavaScript ca o matrice asociativă, iar într-un alt loc din algoritm ca un obiect, să îi atribuiți metodele necesare, să modificați valorile elementelor sale.

Deoarece numele proprietăților și valorile acestora trebuie specificate în format șir atunci când sunt create sau modificate, se recomandă utilizarea notării șirurilor și a ghilimelelor.

Accesarea proprietăților obiectului

Puteți obține și modifica valorile proprietăților unui obiect folosind construcția Object.keys: JavaScript generează o matrice cu toate proprietățile obiectului. Când obiectele sunt create dinamic, această construcție este foarte convenabilă, deoarece generează automat o listă cu toate proprietățile prezente în obiect.

În acest exemplu, două matrice sunt descrise în moduri diferite. În aplicație, ambele tablouri sunt echivalente, deoarece conțin proprietăți cu același nume și valorile lor. Bucla iterează prin toate proprietățile celei de-a doua matrice și generează un șir cu toate valorile.

Un efect similar poate fi obținut în notația punct sau paranteză:

  • x1_Obj.NameLast;
  • x1_Obj [„NameFirst” ].

Ambele constructii sunt valabile si dau rezultatul dorit. În exemplul de mai sus, când se specifică o matrice prin acolade „()”, poate fi făcută o eroare sub forma simbolului "," la sfârșitul enumerarii (marcat în exemplu cu un cerc roșu). Browserele ignoră de obicei caracterul suplimentar din enumerare, dar este mai bine să nu faci asta.

Eliminarea proprietăților obiectului

Deoarece obiectul este un tablou asociativ, operația Obiectul de ștergere JavaScript este executat la nivelul obiectului curent (la moștenire - acest lucru contează) și este luat în considerare în colecția de proprietăți a acestui obiect.

În contextul exemplului de mai sus, puteți utiliza următoarele construcții:

  • delete x1_Obj .NameLast ;
  • șterge x2_Obj ["NameFirst"];

Prima construcție îndepărtează al doilea element al primului obiect, a doua construcție îndepărtează primul element al celui de-al doilea obiect. Operatorul de ștergere nu funcționează pe proprietățile prototipului și returnează false dacă proprietatea nu poate fi ștearsă.

Proprietăți și metode ale obiectelor

Sintaxa proprietăților și funcțiilor obiectelor JavaScript (metode) este similară cu canoanele generale de sintaxă și semantică ale limbajului. De fapt, contrariul este adevărat.

Proprietățile și metodele unui obiect sunt o variantă de descriere a informațiilor și a acțiunilor permise cu aceasta prin paradigma JavaScript orientată pe obiecte.

Acest exemplu descrie un obiect x3_Obj care are doar două proprietăți: item și pos. Apoi metoda hello() a fost adăugată ca funcție. Ca urmare, valorile obiectelor JavaScript vor interpreta această descriere în contextul valorilor proprietăților, așa cum se arată în fereastra de rezultat, adică va plasa corpul funcției (1) ca valoare.

Când se apelează direct proprietatea Hello(), aceasta este interpretată ca metodă (funcție), iar rezultatul (2) este execuția codului acestei metode.

Acest cuvânt cheie într-un obiect

Pentru a naviga în spațiul de proprietate al unui obiect, un dezvoltator poate folosi cuvântul cheie this și se poate referi prin el la proprietățile pe care le descrie pentru a obține sau a modifica valorile acestora.

Acesta este doar începutul descrierii unui obiect cu doar un corp de constructor. Acest exemplu descrie un obiect pentru lucrul cu cookie-uri. Obiectul este inițializat când pagina este încărcată de următorul construct:

  • var oCookie = new scCookies(cOwnerCode);
  • oCookie.Init();

În acest exemplu, cOwnerCode este codul unic de vizitator. Dacă nu este acolo, atunci va fi creat cod nou în constructorul obiectului oCookie. Nu contează ce a vrut să spună dezvoltatorul acestui obiect prin autorizarea vizitatorului, ceea ce este important este modul în care acest cuvânt cheie este folosit aici pentru a descrie metodele obiectului și cum sunt numite din alte metode ale obiectului:

  • acest .GetCookie = function (cName) ( ... );
  • acest .SetCookie = funcție (cName, cValue) ( ​​​​... ).

Acesta descrie metodele obiectului pentru citirea unui cookie după numele său și scrierea valorii unui cookie cu un nume specific.

  • acest .GetCookie("cOwner");
  • acest .SetCookie ( „cOwner” , coOwner );

Asa se folosesc, daca in urma primei constructii nu este reprezentata valoarea, atunci a doua constructie o stabileste.

Un exemplu de obiect pentru lucrul cu cookie-uri

Puteți discuta despre obiect și despre paradigma unei abordări de limbaj orientat pe obiecte care rulează într-un mediu de browser. Este interesant, dar în realitate necesită practică, nu teorie. Servirea DOM-ului unei pagini, oferind instrumente pentru manipularea obiectelor și navigarea prin sisteme de obiecte este un punct forte JavaScript.

În practica orientată pe obiect, altceva este important. Lucrul cu cookie-uri pe aproape toate resursele web este normal pentru curs. Implementarea acestui lucru în format obiect este o idee grozavă. În acest context, inițializarea obiectului are loc în momentul deschiderii paginii: pagina este încărcată = obiectul cookie există și totul a fost citit, iar ceea ce nu era acolo este creat.

În timp ce lucrează cu pagina, vizitatorul efectuează anumite acțiuni, iar browserul trebuie să schimbe sau să creeze cookie-uri. Există două metode obiect (etichetate mai sus) care fac acest lucru.

De fapt, obiectul cookie apare imediat după ce browserul construiește DOM-ul și adaugă noi funcționalități sistemului de obiecte JavaScript: citirea și crearea (modificarea) cookie-urilor.

Acest exemplu simplu este considerat ca o procedură de creare a obiectelor reale care au exclusiv proprietăți și funcționalități (metode) proprii. Fiecare obiect își face treaba și nu participă la algoritmul general, nu modifică datele altor obiecte sau spațiul de nume comun.

Cu această abordare, dezvoltatorul asigură crearea unui sistem de obiecte unice suficient pentru a descrie și deservi problema rezolvată.

Evenimente de pagină și obiect

Un element important al funcționării DOM-ului și JavaScript: object event "s - care vă permite să obțineți informații despre un eveniment în handlerul său. Aproape fiecărui element de pagină i se poate atribui propriul handler pentru unul sau mai multe evenimente.

De fapt, un dezvoltator JavaScript nu creează o „piesă” mare de cod, ci multe descrieri ale funcțiilor, obiectelor, structurilor de date și atribuie handlere de evenimente unor elemente specifice ale paginii.

Evenimentul obiect este informații despre evenimentul care a determinat handler-ul și capacitatea acestui handler de a efectua un răspuns adecvat la acest eveniment. Fiecare eveniment diferă nu numai prin numele și locul de apariție, ci și prin mulți alți parametri.

În special, evenimentele de la tastatură sunt un set de parametri, evenimentele mouse-ului sunt o gamă complet diferită de date, iar răspunsul serverului prin AJAX este complet planificat de către dezvoltator însuși.

În fiecare caz specific, imaginea evenimentelor care pot apărea pe pagină este transformată într-o serie de handlere activate; în afara opțiunilor furnizate pentru procesarea unui set specific de evenimente, pagina nu întreprinde nicio acțiune.

Crearea și funcționarea obiectelor

Browserul „transformă” URI-ul, adresa resursei web specificate de vizitator, în arborele DOM - sistemul de obiecte al paginii acestei resurse web. Atunci când un vizitator urmărește link-uri de pe o pagină, browserul merge la arborii corespunzătoare a altor pagini.

Această împrejurare permite dezvoltatorului să-și construiască sistemul de obiecte ca fundație a unei resurse web care răspunde în mod adecvat la comportamentul vizitatorilor. Dacă izolați funcționalitatea generală, de exemplu:

  • lucrul cu cookie-uri;
  • primirea/transmiterea datelor (AJAX);
  • sfaturi cu instrumente;
  • mesaje interne (chat pe site);
  • alte sarcini;

apoi, odată create, sistemele de obiecte pot fi folosite la dezvoltarea altor site-uri. Acesta este un avantaj semnificativ al abordării obiectului față de utilizarea obișnuită a JavaScript ca limbaj de browser care asigură funcționarea paginii și răspunsul la evenimente.

Obiectele sunt componente complete care pot fi formatate ca fișiere separate și utilizate în viitor. O trăsătură caracteristică a acestei abordări este posibilitatea de feedback, atunci când un obiect actualizat, îmbunătățit poate fi utilizat într-o dezvoltare anterioară, actualizându-și automat funcționalitatea fără modificarea site-ului.




Obiectele sunt unul dintre conceptele de bază în JavaScript. Când am început să le studiez, mi s-au părut destul de simple: doar perechi de chei și valori, așa cum este descris în teorie.

Abia după ceva timp am început să înțeleg că subiectul era mult mai complex decât credeam. Și apoi am început să studiez informații din diferite surse. Unii dintre ei au dat o idee bună despre subiect, dar nu am reușit să văd întreaga imagine imediat.

În această postare, am încercat să acopăr toate aspectele lucrului cu obiecte în JS, fără a intra prea adânc în detalii specifice, dar și fără a omite detalii importante care vă vor ajuta să înțelegeți subiectul și să vă simțiți mai încrezători pe măsură ce îl studiați mai departe.

Deci, să începem cu elementele de bază.

Un obiect

Un obiect în JavaScript este pur și simplu o colecție de proprietăți, fiecare dintre acestea fiind o pereche cheie-valoare. Puteți accesa tastele folosind un punct ( obj.a) sau notația paranteze ( obj["a"]).

Amintiți-vă că ar trebui folosite paranteze dacă cheia este:

  • nu este un identificator JavaScript valid (are un spațiu, o liniuță, începe cu un număr...)
  • este o variabilă.
Una dintre proprietățile pe care obiectele din JS le primesc atunci când sunt create este apelată Prototip, iar acesta este un concept foarte important.

Prototip

Fiecare obiect din JavaScript are o proprietate internă numită Prototip. În majoritatea browserelor vă puteți referi la el prin notație __proto__.

Prototip este o modalitate de a impune moștenirea proprietăților în JavaScript. În acest fel, puteți partaja funcționalitatea fără a duplica codul din memorie. Metoda funcționează prin crearea unei conexiuni între două obiecte.

Mai simplu spus, Prototype creează un indicator de la un obiect la altul.

Lanț prototip

De fiecare dată când JS caută o proprietate într-un obiect și nu o găsește direct pe obiectul în sine, verifică prezența proprietății în obiectul prototip. Dacă nu există nicio proprietate în el, atunci JS va continua să caute în prototipul obiectului asociat. Acest lucru va continua până când JS găsește o proprietate potrivită sau ajunge la sfârșitul lanțului.

Să ne uităm la un exemplu:

Var cons = function () ( this.a = 1; this.b = 2; ) var obj = new cons(); cons.prototype.b = 3; cons.prototype.c = 4;
contra este un constructor (pur și simplu o funcție care poate fi apelată folosind operatorul nou).

Pe a cincea linie creăm un nou obiect - o nouă copie contra. Imediat după creare obj primește și proprietatea prototipului.

Și acum adăugăm proprietăți ( "b", "c") prototip de obiect contra.
Sa luam in considerare obj:

obj.a // 1- totul este la fel aici, obj.a este inca 1.
obj.c?- y obj nicio proprietate c! Cu toate acestea, așa cum sa menționat anterior, JS îl va căuta acum în prototip objși va returna valoarea 4.

Acum să ne gândim care este sensul obj.bși cum va fi când vom elimina obj.b?

Obj.b este egal cu 2. Am atribuit proprietatea b, dar am făcut-o pentru un prototip contra, deci când verificăm obj.b, atunci primim în continuare 2. Cu toate acestea, imediat după eliminare obj.b JS nu va mai putea găsi b y o B j, și, prin urmare, va continua căutarea în prototip și va returna valoarea 3.

Crearea unui obiect

Literal obiect: fie obj = (a: 1);
Am creat un obiect cu următorul lanț de prototipuri: obj ---> Object.prototype ---> null
După cum poți ghici, obiect.prototip este prototipul obiectului și, de asemenea, capătul lanțului de prototipuri.

Object.create():var newObj = Object.create(obj);
U nouObj va exista următorul lanț de prototipuri: newObj ---> obj ---> Object.prototype ---> null

Constructor. Ca și în exemplul de mai sus, constructorul este pur și simplu o funcție JS care ne permite să profităm de operator nou pentru a crea noi instanțe ale acestuia.

Clasele ES6:

Clasa dreptunghi ( constructor(height, width) ( this.height = height; this.width = width; ) getArea() ( return this.height * this.width; ) ) let square = new rectangle(2, 2);
Pătrat- instanță de constructor dreptunghi, și așa putem suna square.getArea() //4, pătrat.lăţime, precum și toate funcțiile moștenite de la obiect.prototip.

Care este mai bine? Dacă intenționați să creați mai multe instanțe, puteți utiliza ES6 sau designerul. Dacă intenționați să creați obiectul o dată, atunci este mai bine să specificați un literal, deoarece acesta este cel mai simplu mod.

Și acum că am aflat despre prototipși după ce ne-am familiarizat cu toate modalitățile de a crea noi obiecte, putem trece la discutarea despre unul dintre cele mai confuze aspecte asociate obiectelor.

Comparați și schimbați obiecte

În JavaScript, obiectele sunt de tip referință

Când creăm un obiect fie obj = (a: 1);, variabil obj primește adresa de memorie a obiectului, dar nu valoarea acestuia! Este extrem de important să înțelegeți această diferență, altfel pot apărea erori. Când creăm un alt obiect let newObj = obj, noi de fapt creăm indicator la o anumită zonă de memorie obj, și nu un obiect complet nou.

Aceasta înseamnă că făcând newObj.a = 2, ne schimbam de fapt obj astfel încât obj.a devine egal cu 2!

Această abordare duce cu ușurință la erori, motiv pentru care multe companii lucrează cu obiecte imuabile. În loc să modificați un obiect deja creat, va trebui să creați din nou un obiect nou (o copie a originalului) și să-i faceți modificări. Acesta este cât de importante funcționează bibliotecile precum Redux și este unul dintre conceptele de bază ale programării funcționale în general. Puteți citi mai multe.

Egalitatea

De asemenea, din cele de mai sus rezultă că două obiecte nu pot fi niciodată egale, chiar dacă au aceleași proprietăți. Acest lucru se datorează faptului că JS compară de fapt locația de memorie a obiectelor și două obiecte nu se află niciodată în aceeași locație de memorie.

// Două obiecte distincte cu aceleași proprietăți nu sunt egale var fruit = (nume: „măr”); var fruitbear = (nume: „măr”); fruct === fructbear; // returnează fals // aici fruit și fruitbear indică același obiect var fruit = (nume: „măr”); var fruitbear = fruct; fruct === fructbear; // returnează adevărat
Deci, cel mai probabil v-ați întrebat deja cum puteți compara obiecte sau cum să efectuați diverse manipulări cu obiecte, având în vedere cerința de imuabilitate a acestora.

Să luăm în considerare mai multe posibilități.

Schimbarea unui obiect

Să presupunem că este clar că nu ar trebui să schimbăm obiectele, așa că dorim să creăm o copie a obiectului corespunzător și să îi schimbăm proprietățile. Vine în ajutor Object.assign().

Var obj = (a: 1, b: 2); var newObj = Object.assign((), obj,(a:2)) // (a: 2, b: 2 )
Daca vrem sa schimbam valoarea proprietatii A obiect obj, poți să folosești obiect.atribuie pentru a crea o copie objși modificările ei.

În exemplu puteți vedea că mai întâi creăm un obiect gol, apoi copiam valorile objși să ne facem modificările, obținând în cele din urmă un obiect nou și gata de utilizare.

Vă rugăm să rețineți că această metodă nu va funcționa pentru copierea profundă. Când vorbim despre copierea profundă, ne referim la faptul că trebuie să copiem un obiect cu una sau mai multe proprietăți.

Const obj = (a: 1, b: ( a: 1 ) ); // proprietatea b este un obiect
Object.assign() copiează proprietățile unui obiect, deci dacă valoarea proprietății este un pointer către un obiect, atunci doar pointerul este copiat.

O copie profundă necesită o operație recursivă. Aici puteți scrie o funcție sau pur și simplu utilizați o metodă _.cloneDeep din biblioteca Lodash.

Compararea obiectelor

Există o tehnică grozavă de lucru cu obiecte - conversia șirurilor. În exemplul următor, convertim ambele obiecte în șiruri de caractere și le comparăm:

JSON.stringify(obj1) === JSON.stringify(obj2)
Această abordare are sens, deoarece ajungem să comparăm șiruri care sunt un pointer către un tip de valoare. Vestea proastă este că nu funcționează întotdeauna, în principal pentru că ordinea proprietăților obiectului nu este garantată.

O altă soluție bună este să folosiți metoda _.este egal de la Lodash, care realizează o comparație profundă a obiectelor.

Și înainte de a încheia, să trecem peste câteva întrebări frecvente despre obiecte. Acest lucru vă va ajuta să vă scufundați mai adânc în subiect și să aplicați cunoștințele dobândite în practică.

Încercați să vă gândiți la soluție înainte de a citi răspunsul.

Cum să afli lungimea unui obiect?

Pentru a obține răspunsul, trebuie să parcurgeți toate proprietățile obiectului una câte una și să le numărați. Există mai multe moduri de a efectua o astfel de iterație:
  • pentru in. Această metodă acoperă toate proprietățile numărabile ale unui obiect și lanțurile sale prototip. Am văzut prototipul (și sperăm că am învățat materialul), așa că ar trebui să fie clar că aplicația pentru in nu va fi întotdeauna adevărat pentru obținerea proprietăților unui obiect.
  • Obiect.chei. Această metodă returnează o matrice cu cheile tuturor proprii(aparținând obiectului specificat) socoteală proprietăți. Această abordare este mai bună deoarece lucrăm doar pe proprietățile obiectului, fără a accesa proprietățile prototip. Există, totuși, situații în care ați atribuit un atribut enumerabil unele proprietăți sunt false și obiect.chei ajunge să o săriți peste el și obțineți un rezultat incorect. Acest lucru se întâmplă rar, dar în astfel de cazuri va fi util getOwnPropertyNames.
  • getOwnPropertyNames returnează o matrice care conține totul proprii chei obiect (atât numărabile, cât și nenumărabile).
De asemenea, merită menționat:
  • Obiect.valori iterează peste propriile proprietăți de numărare și returnează o matrice cu cele corespunzătoare valorile.
  • Obiect.intrari iterează peste propriile proprietăți de numărare și returnează o matrice cu cheile și valorile acestora.
Cred că ați observat că majoritatea metodelor enumerate mai sus returnează o matrice. Aceasta este o oportunitate de a profita din plin de tehnicile de matrice JavaScript.

O astfel de metodă este matrice.lungime. Drept urmare, putem scrie pur și simplu

Fie objLength = Object.getOwnPropertyNames(obj).length;

Cum se verifică dacă un obiect este gol?

  1. JSON.stringify(myObj) === „()”?. Aici folosim din nou instrumentul de conversie a șirurilor pentru a verifica cu ușurință dacă un obiect este gol (comparând șiruri, nu obiecte).
  2. !Object.keys(myobj).length // adevărat?.? După cum am menționat, conversia cheilor obiectului într-o matrice poate fi foarte utilă. Aici folosim proprietatea convenabilă lungime, moștenit de la Array.prototip, folosindu-l pentru a verifica lungimea cheilor din matrice. În JS 0 se transformă în fals, deci adăugând ! îl transformăm în adevărat. Orice alte numere vor fi convertite în false.

In cele din urma

Sper că acum vă simțiți mai încrezători în crearea și lucrul cu obiecte. Să rezumăm:
  • Rețineți că obiectele sunt de tip referință, ceea ce înseamnă că este recomandat să lucrați cu ele fără a modifica obiectele originale.
  • Fă-ți prietenii cu proprietatea prototipși un lanț de prototipuri.
  • Cunoașteți instrumentele care vă ajută să lucrați cu obiecte. Amintiți-vă că puteți transforma obiectele în șiruri de caractere, puteți obține o matrice de chei ale acestora sau pur și simplu puteți itera proprietățile lor folosind setul de metode la care am fost introdus.
Succes în învățarea obiectelor JavaScript.

JavaScript este proiectat pe o paradigmă simplă bazată pe obiecte. Un obiect este o colecție de proprietăți, iar o proprietate este o asociere între un nume (sau cheie) și o valoare. Valoarea unei proprietăți poate fi o funcție, caz în care proprietatea este cunoscută ca metodă. În plus față de obiectele care sunt predefinite în browser, puteți defini propriile obiecte. Acest capitol descrie cum să utilizați obiecte, proprietăți, funcții , și metode și cum să vă creați propriile obiecte.

Privire de ansamblu asupra obiectelor

Obiectele din JavaScript, la fel ca în multe alte limbaje de programare, pot fi comparate cu obiectele din viața reală. Conceptul de obiecte în JavaScript poate fi înțeles cu obiecte reale, tangibile.

În JavaScript, un obiect este o entitate independentă, cu proprietăți și tip. Compară-l cu o ceașcă, de exemplu. O ceașcă este un obiect, cu proprietăți. O ceașcă are o culoare, un design, o greutate, un material din care este făcută etc. În același mod, obiectele JavaScript pot avea proprietăți, care le definesc caracteristicile.

Obiecte și proprietăți

Un obiect JavaScript are proprietăți asociate cu acesta. O proprietate a unui obiect poate fi explicată ca o variabilă care este atașată obiectului. Proprietățile obiectelor sunt practic aceleași cu variabilele JavaScript obișnuite, cu excepția atașării la obiecte. Proprietățile unui obiect definesc caracteristicile obiectului. Accesați proprietățile unui obiect cu o simplă notație de puncte:

ObjectName.propertyName

La fel ca toate variabilele JavaScript, atât numele obiectului (care ar putea fi o variabilă normală), cât și numele proprietății sunt sensibile la majuscule și minuscule. Puteți defini o proprietate atribuindu-i o valoare. De exemplu, să creăm un obiect numit myCar și să-i dăm proprietăți numite make , model , and year, după cum urmează:

Var myCar = obiect nou(); myCar.make = „Ford”; myCar.model = „Mustang”; myCar.year = 1969; myCar.color; // nedefinit

Proprietățile obiectelor JavaScript pot fi, de asemenea, accesate sau setate folosind o notație paranteze (pentru mai multe detalii vezi Accesorii de proprietate). Obiectele sunt uneori numite tablouri asociative, deoarece fiecare proprietate este asociată cu o valoare șir care poate fi folosită pentru a o accesa. Deci, de exemplu, puteți accesa proprietățile obiectului myCar după cum urmează:

MyCar["make"] = "Ford"; myCar["model"] = "Mustang"; myCar["anul"] = 1969;

Numele unei proprietăți de obiect poate fi orice șir JavaScript valid sau orice poate fi convertit într-un șir, inclusiv șirul gol. Cu toate acestea, orice nume de proprietate care nu este un identificator JavaScript valid (de exemplu, un nume de proprietate care are un spațiu sau o cratimă sau care începe cu un număr) poate fi accesat numai folosind notația paranteze pătrate. Această notație este, de asemenea, foarte utilă atunci când numele proprietăților urmează să fie determinate dinamic (când numele proprietății nu este determinat până la runtime). Exemplele sunt următoarele:

// patru variabile sunt create și atribuite dintr-o singură mișcare, // separate prin virgule var myObj = new Object(), str = "myString", rand = Math.random(), obj = new Object(); myObj.type = "Sintaxa punctului"; myObj["date created"] = "Șir cu spațiu"; myObj = „Valoare șir”; myObj = „Număr aleatoriu”; myObj = „Obiect”; myObj[""] = "Chiar și un șir gol"; console.log(myObj);

Vă rugăm să rețineți că toate cheile din notația dintre paranteze drepte sunt convertite în șir, cu excepția cazului în care sunt simboluri, deoarece numele proprietăților obiectelor JavaScript (cheile) pot fi doar șiruri de caractere sau simboluri (la un moment dat, numele private vor fi adăugate și ca propunere de câmpuri de clasă). progresează, dar nu le vei folosi cu formă). De exemplu, în codul de mai sus, când cheia obj este adăugată la myObj , JavaScript va apela metoda obj.toString() și va folosi acest șir de rezultat ca nouă cheie.

De asemenea, puteți accesa proprietăți folosind o valoare șir care este stocată într-o variabilă:

Var propertyName = "facă"; myCar = "Ford"; propertyName = "model"; myCar = „Mustang”;

Folosind o funcție de constructor

Alternativ, puteți crea un obiect cu acești doi pași:

  1. Definiți tipul obiectului scriind o funcție de constructor. Există o convenție puternică, cu un motiv întemeiat, de a folosi o literă inițială majusculă.
  2. Creați o instanță a obiectului cu new .

Pentru a defini un tip de obiect, creați o funcție pentru tipul de obiect care îi specifică numele, proprietățile și metodele. De exemplu, să presupunem că doriți să creați un tip de obiect pentru mașini. Doriți ca acest tip de obiect să se numească Car , și doriți să aibă proprietăți pentru marcă, model și an. Pentru a face acest lucru, ar trebui să scrieți următoarea funcție:

Funcția Mașină (marcă, model, an) ( this.make = make; this.model = model; this.year = year; )

Observați utilizarea acestuia pentru a atribui valori proprietăților obiectului pe baza valorilor transmise funcției.

Acum puteți crea un obiect numit mycar după cum urmează:

Var mycar = masina noua("Eagle", "Talon TSi", 1993);

Această declarație creează mycar și îi atribuie valorile specificate pentru proprietățile sale. Apoi valoarea mycar.make este șirul „Eagle”, mycar.year este numărul întreg 1993 și așa mai departe.

Puteți crea orice număr de obiecte Car prin apeluri la noi. De exemplu,

Var kenscar = mașină nouă ("Nissan", "300ZX", 1992); var vpgscar = masina noua("Mazda", "Miata", 1990);

Un obiect poate avea o proprietate care este el însuși un alt obiect. De exemplu, să presupunem că definiți un obiect numit persoană după cum urmează:

Funcție Persoană (nume, vârstă, sex) ( this.name = nume; this.age = vârstă; this.sex = sex; )

și apoi instanțiați două noi obiecte persoană după cum urmează:

Var rand = new Person("Rand McKinnon", 33, "M"); var ken = new Person("Ken Jones", 39, "M");

Apoi, puteți rescrie definiția Car pentru a include o proprietate de proprietar care ia un obiect persoană, după cum urmează:

Funcția Mașină (marcă, model, an, proprietar) ( this.make = make; this.model = model; this.year = year; this.owner = proprietar; )

Pentru a instanția noile obiecte, utilizați următoarele:

Var car1 = new Car("Vultur", "Talon TSi", 1993, rand); var car2 = new Car("Nissan", "300ZX", 1992, ken);

Observați că în loc să treacă un șir literal sau o valoare întreagă la crearea noilor obiecte, instrucțiunile de mai sus trec obiectele rand și ken ca argumente pentru proprietari. Apoi, dacă doriți să aflați numele proprietarului car2, puteți accesa următoarea proprietate:

Auto2.nume.proprietar

Rețineți că puteți adăuga oricând o proprietate unui obiect definit anterior. De exemplu, afirmația

Car1.color = „negru”;

adaugă o culoare de proprietate la car1 și îi atribuie o valoare „negru”. Cu toate acestea, acest lucru nu afectează niciun alt obiect. Pentru a adăuga noua proprietate la toate obiectele de același tip, trebuie să adăugați proprietatea la definiția tipului de obiect Car.

Folosind metoda Object.create

Vezi si

  • Pentru a explora mai adânc, citiți despre detaliile modelului de obiecte javaScript.
  • Pentru a afla despre clasele ECMAScript 2015 (o nouă modalitate de a crea obiecte), citiți capitolul Clasele JavaScript.

Ultima actualizare: 04/08/2018

Programarea orientată pe obiecte este una dintre paradigmele dominante în dezvoltarea aplicațiilor de astăzi, iar în JavaScript putem profita din plin de OOP. În același timp, în raport cu JavaScript, programarea orientată pe obiecte are câteva caracteristici.

Obiecte

În subiectele anterioare, am lucrat cu date primitive - numere, șiruri de caractere, dar datele nu reprezintă întotdeauna tipurile primitive. De exemplu, dacă în programul nostru trebuie să descriem esența unei persoane care are un nume, vârstă, sex și așa mai departe, atunci în mod natural nu vom putea reprezenta esența unei persoane ca un număr sau șir. Vom avea nevoie de câteva rânduri sau numere pentru a descrie corect persoana. În acest sens, o persoană va acționa ca o structură complexă complexă, care va avea proprietăți individuale - vârstă, înălțime, prenume, nume etc.

Pentru a lucra cu astfel de structuri, JavaScript folosește . Fiecare obiect poate stoca proprietăți care îi descriu starea și metode care îi descriu comportamentul

Crearea unui nou obiect

Există mai multe moduri de a crea un obiect nou.

Prima modalitate este să utilizați constructorul Object:

Var user = obiect nou();

În acest caz, obiectul se numește utilizator. Este definită în același mod ca orice variabilă obișnuită folosind cuvântul cheie var.

Noua expresie Object() reprezintă un apel către un constructor, o funcție care creează un nou obiect. Noul operator este folosit pentru a apela constructorul. Apelarea unui constructor este, în esență, ca apelarea unei funcții obișnuite.

A doua modalitate de a crea un obiect este prin utilizarea acoladelor:

Var user = ();

Astăzi, a doua metodă este mai comună.

Proprietățile obiectului

După crearea unui obiect, putem defini proprietățile acestuia. Pentru a defini o proprietate, trebuie să specificați numele proprietății după numele obiectului, separate printr-un punct și să îi atribuiți o valoare:

Var user = (); user.name = "Tom"; user.age = 26;

În acest caz, două proprietăți numele și vârsta sunt declarate și li se atribuie valori corespunzătoare. După aceea, putem folosi aceste proprietăți, de exemplu, pentru a le afișa valorile în consolă:

Console.log(nume.utilizator); console.log(user.age);

De asemenea, puteți defini proprietăți atunci când definiți un obiect:

Var user = (nume: „Tom”, vârsta: 26);

În acest caz, un caracter două puncte este folosit pentru a atribui o valoare proprietății și o virgulă (mai degrabă decât un punct și virgulă) este plasată după definiția proprietății.

În plus, există o cale rapidă de a defini proprietățile:

Var nume = "Tom"; var varsta = 34; var user = (nume, varsta); console.log(nume.utilizator); // Tom console.log(user.age); // 34

În acest caz, numele variabilelor sunt și numele proprietăților obiectului. Și astfel puteți crea modele mai complexe:

Var nume = "Tom"; var varsta = 34; var user = (nume, varsta); var profesor = (utilizator, curs: "JavaScript"); console.log(profesor.utilizator); // (nume: „Tom”, vârsta: 34) console.log(profesor.curs); // JavaScript

Metode obiect

Metodele unui obiect definesc comportamentul sau acțiunile pe care le efectuează. Metodele sunt funcții. De exemplu, să definim o metodă care să afișeze numele și vârsta unei persoane:

Var user = (); user.name = "Tom"; user.age = 26; user.display = function())( console.log(user.name); console.log(user.age); ); // apel de metodă user.display();

Ca și în cazul funcțiilor, metodele sunt mai întâi definite și apoi apelate.

De asemenea, metodele pot fi definite direct la definirea unui obiect:

Var user = (nume: „Tom”, vârsta: 26, afișare: function())( console.log(this.name); console.log(this.age); ) );

Ca și în cazul proprietăților, unei metode i se atribuie o referință de funcție folosind două puncte.

Pentru a accesa proprietățile sau metodele unui obiect din acel obiect, utilizați cuvântul cheie this. Înseamnă o referire la obiectul curent.

Puteți utiliza, de asemenea, o modalitate prescurtată de a defini metode, omițând două puncte și funcția:

Var user = (nume: „Tom”, vârsta: 26, display())( console.log(this.name, this.age); ), move(place)( console.log(this.name, „goes to " , loc); ) ); user.display(); // Tom 26 user.move("magazinul"); //Tom merge la magazin

Sintaxa matricei

Există, de asemenea, o modalitate alternativă de a defini proprietăți și metode folosind sintaxa matricei:

Var user = (); user["name"] = "Tom"; utilizator["varsta"] = 26; user["display"] = function())( console.log(user.name); console.log(user.age); ); // apelarea metodei user["display"]();

Numele fiecărei proprietăți sau metode este cuprins între ghilimele și paranteze pătrate, apoi i se atribuie și o valoare. De exemplu, user["varsta"] = 26 .

Când accesați aceste proprietăți și metode, puteți fie să utilizați notația cu puncte (user.name) fie să utilizați: user["name"]

Șiruri de caractere ca proprietăți și metode

De asemenea, trebuie remarcat faptul că numele proprietăților și metodelor obiectelor sunt întotdeauna șiruri. Adică, am putea rescrie definiția anterioară a obiectului astfel:

Var user = ( „nume”: „Tom”, „vârstă”: 26, „afișare”: function())( console.log(nume utilizator); console.log(user.varsta); ) ); // apel de metodă user.display();

Pe de o parte, nu există nicio diferență între cele două definiții. Pe de altă parte, există cazuri în care includerea titlului într-o linie poate ajuta. De exemplu, dacă numele proprietății constă din două cuvinte separate printr-un spațiu:

Var user = (nume: „Tom”, vârsta: 26, „nume complet”: „Tom Johns”, „afișează informații”: function())( console.log(user.name); console.log(user.age ) ; ) ); console.log(utilizator[„nume complet”]); utilizator["afișează informații"]();

Numai în acest caz, pentru a accesa astfel de proprietăți și metode, trebuie să folosim sintaxa matricei.

Îndepărtarea proprietăților

Mai sus ne-am uitat la modul în care putem adăuga dinamic noi proprietăți unui obiect. Cu toate acestea, putem șterge și proprietăți și metode folosind operatorul de ștergere. Și la fel ca și în cazul adăugării, putem elimina proprietăți în două moduri. Prima modalitate este de a utiliza notația punct:

Șterge obiect.proprietate

Sau utilizați sintaxa matricei:

Șterge obiect[„proprietate”]

De exemplu, să eliminăm proprietatea:

Var user = (); user.name = "Tom"; user.age = 26; user.display = function())( console.log(user.name); console.log(user.age); ); console.log(nume.utilizator); // Tom șterge user.name; // ștergeți proprietatea // alternativă // ​​ștergeți utilizatorul["nume"]; console.log(nume.utilizator); // nedefinit

După ștergere, proprietatea va fi nedefinită, așa că atunci când încercați să o accesați, programul va returna valoarea nedefinită.

În acest articol, vreau să vorbesc cât mai complet și consecvent posibil despre ce este un obiect în JavaScript, care sunt capabilitățile acestuia, ce relații pot fi construite între obiecte și ce metode de moștenire „nativă” decurg din aceasta, cum afectează toate acestea. performanta si ce sa faci in general cu toate astea :)

Articolul NU va avea un cuvânt despre: emularea paradigmei tradiționale de clasă-obiect, zahăr sintactic, învelișuri și cadre.

Complexitatea materialului va crește de la începutul până la sfârșitul articolului, așa că pentru profesioniști primele părți pot părea plictisitoare și banale, dar apoi va fi mult mai interesant :)

Obiecte în JavaScript

Multe articole conțin expresia „În JavaScript, totul este un obiect”. Din punct de vedere tehnic, acest lucru nu este în întregime adevărat, dar face impresia potrivită începătorilor :)

Într-adevăr, mult în limbaj este un obiect și chiar și ceea ce nu este un obiect poate avea unele dintre capacitățile sale.

Este important să înțelegem că cuvântul „obiect” este folosit aici nu în sensul „un obiect al unei clase”. Un obiect în JavaScript este în primul rând doar o colecție de proprietăți (dacă preferați, îl puteți numi o matrice sau o listă asociativă) constând din perechi cheie-valoare. Mai mult, cheia poate fi doar un șir (chiar și pentru elemente de matrice), dar valoarea poate fi orice tip de date enumerate mai jos.

Deci, în JavaScript există 6 tipuri de bază datele sunt Nedefinite (indicând absența unei valori), Null, Boolean (Boolean), String (șir), Number (număr) și Object (obiect).
Mai mult, primele 5 sunt primitiv tipuri de date, dar Object nu este. În plus, putem considera convențional că tipul Object are „subtipuri”: matrice (Matrice), funcție (Funcție), expresie regulată (RegExp) și altele.
Aceasta este o descriere oarecum simplificată, dar în practică este de obicei suficientă.

În plus, tipurile primitive String, Number și Boolean sunt legate în anumite moduri de „subtipurile” neprimitive ale Object: String, Number și, respectiv, Boolean.
Aceasta înseamnă că șirul „Hello, world”, de exemplu, poate fi creat fie ca valoare primitivă, fie ca obiect String.
Pe scurt, acest lucru se face astfel încât programatorul să poată folosi metode și proprietăți atunci când lucrează cu valori primitive ca și cum ar fi obiecte. Puteți citi mai multe despre acest lucru în secțiunea corespunzătoare a acestui articol.

Lucrați de pe link

O referință este un mijloc de accesare a unui obiect sub diferite nume. Lucrul cu orice obiect se efectuează exclusiv prin referință.
Să demonstrăm acest lucru cu un exemplu:
test= funcția () (alertă ("Bună ziua!" )) //Creează o funcție (alertă ("Bună ziua!")) (și o funcție, după cum ne amintim, este un obiect cu drepturi depline) și face din variabila de test o referință la ea
test_link=test; //test_link se referă acum și la funcția noastră
Test(); //Buna ziua!
test_link(); //Buna ziua!


După cum putem vedea, atât prima legătură cât și a doua dau același rezultat.
Trebuie să realizăm că nu avem nicio funcție numită test și că variabila de test nu este un fel de legătură „principală” sau „primară”, iar „test_link” este una minoră.

Funcția noastră, ca orice alt obiect, este pur și simplu o zonă din memorie și toate referințele la această zonă sunt absolut echivalente. Mai mult decât atât, este posibil ca obiectul să nu aibă deloc referințe - în acest caz se numește anonim și poate fi folosit numai imediat după creare (de exemplu, trecut la o funcție), altfel va fi imposibil de accesat și va fi în curând fi distrus de garbage collector (colectare de gunoi), care este responsabil pentru ștergerea obiectelor fără referințe.

Să vedem de ce este atât de important să înțelegem asta:

test=(prop: "un text") //Creează un obiect cu proprietatea prop
test_link=test; //Creează un alt link către acest obiect

Alert(test.prop); //un text

//Modifică proprietatea obiectului
test_link.prop="newtext" ;

Alert(test.prop); //text nou
alert(test_link.prop); //text nou
/*S-ar putea spune că proprietatea s-a schimbat atât aici, cât și acolo - dar nu este așa.
Există un singur obiect. Deci proprietatea s-a schimbat o dată pe ea, iar linkurile continuă doar să indice acolo unde indică. */

//Adăugați o nouă proprietate și eliminați-o pe cea veche
test.new_prop="bună ziua" ;
şterge test.prop;

Alertă(test_link.prop); //undefined - această proprietate nu mai există
alert(test_link.new_prop);

//Ștergeți linkul
sterge testul;
alert(test.new_prop);
/*În acest moment, scriptul va arunca o eroare, deoarece testul nu mai există, iar test.new_prop nu există cu atât mai mult */
alert(test_link.new_prop); //Buna ziua
/* dar aici totul este în ordine, pentru că nu am șters obiectul în sine, ci doar un link către el. Acum obiectul nostru este indicat de un singur link, test_link */

//Creează un nou obiect
test=test_link; //Mai întâi, creați din nou legătura de testare
test_link=(prop: "un text" ) //Și aici este noul obiect

Alertă(test_link.prop); //un text
alert(test.prop); //nedefinit
/* Crearea unui nou obiect rupe legătura de referință, iar acum test și test_link indică diferite obiecte.
De fapt, acest lucru este echivalent cu ștergerea test_link-ului și crearea lui din nou, dar arătând către un obiect diferit */
alert(test.new_prop); //bună ziua - acum testul conține un link către primul nostru obiect


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

Acest comportament al obiectelor ridică adesea o mulțime de întrebări pentru dezvoltatorii începători, așa că sper că acest text va aduce puțină claritate. Dacă dorim să creăm o copie cu adevărat nouă, independentă a unui obiect, și nu o legătură, atunci singura modalitate de a face acest lucru este să creăm un nou obiect și să copiați acolo proprietățile necesare.

De asemenea, este de remarcat faptul că lucrul cu obiecte prin referință, pe lângă efectele distractive enumerate mai sus, oferă și economii semnificative de memorie, ceea ce este important atunci când un obiect este utilizat pe scară largă în diferite locuri din program.

Valori primitive

După cum am menționat mai sus, tipurile de date String și Number pot fi fie obiecte, fie valori primitive.
obj= new String(„bună ziua”); //Creează un șir ca obiect
simplu="bună ziua"; //Creează o valoare primitivă

Alerta(obj); //Buna ziua
alertă (simplu); //bună ziua - până acum totul este previzibil

Alerta(lungimea obiectului); //6 - un obiect de tip String are o proprietate length care stochează lungimea șirului
alert(simple.length); //6
/* Deși simplu nu este un obiect, putem accesa același set de proprietăți ca și un obiect de tip String. Este destul de convenabil */

Obj.prop="text" ;
simple.prop="text" ;

Alert(obj.prop); //text - deoarece obj este un obiect obișnuit, îi putem oferi cu ușurință o altă proprietate
alert(simple.prop); //nedefinit - dar simplu nu este un obiect, iar acest număr nu va funcționa pentru noi

* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.


Același lucru este valabil atât pentru tipurile Number, cât și pentru Boolean (ei bine, cu excepția faptului că nu au o proprietate de lungime, dar au o serie de alte proprietăți grozave).
Folosirea șirurilor și numerelor ca obiecte nu are niciun beneficiu practic, deoarece valorile primitive sunt mai convenabile de utilizat, dar păstrează în același timp toată funcționalitatea necesară. Cu toate acestea, pentru a completa imaginea, este necesar să înțelegem acest mecanism.

Nu confundați utilizarea valorilor primitive cu utilizarea literalelor - de exemplu, dacă creăm o matrice ca „test=new Array()” sau ca „test=", rezultatul va fi în continuare același obiect. Nu vom primi nicio valoare primitivă.

Crearea și utilizarea obiectelor

Deci, spre deosebire de limbajele care implementează paradigma clasă-obiect, nu trebuie să creăm mai întâi o clasă și apoi să creăm un obiect al clasei. Putem crea imediat un obiect, ceea ce vom face în următorul exemplu:
test=(
simple_property: „Bună ziua”,
proprietate_obiect: (
user_1: „Petya” ,
user_2: „Vasya”
},
function_property: function (utilizator) (
alert(acest .simple_property + ", " + this .object_property);
}
}

Test.function_property("user_1" ); //Bună, Petya.

* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.


Avem un obiect de testare care are 3 proprietăți, ale căror nume, sper, vorbesc de la sine. Ceea ce ne interesează cel mai mult este function_property, care conține funcția. O astfel de funcție poate fi numită o metodă obiect.

Funcția noastră folosește de două ori cuvântul cheie this, care este un pointer (adică, o referință) la obiectul de la care este apelată funcția. Astfel, this.simple_property=test.simple_property="Bună ziua", și this.object_property=test.object_property="Peter".

Este important să fie clar că acest lucru indică întotdeauna obiectul de la care este apelată funcția și nu obiectul căruia îi aparține. Deși în acest exemplu sunt același obiect, nu este întotdeauna cazul.

test.function_property("utilizator_1"); //Bună, Petya.

Test2=Obiect nou(); //O altă formă de a crea un obiect nou, similar cu test2=()

Test.function_property.call(test2, "user_1" ); //eroare
/* Metoda de apel vă permite să apelați o funcție în numele altui obiect. În acest caz, numim metoda function_property a obiectului de testare, iar aceasta nu mai indică la obiectul de testare, ci la obiectul test2. Și pentru că nu are proprietatea object_property, atunci când încercați să obțineți this.object_property scriptul va arunca o eroare */

//să încercăm să remediam situația
test2.simple_property="O zi bună" ;
test2.object_property=test.object_property; //În acest caz, vom folosi specificarea obiectului prin referință pentru a nu duplica codul

Test.function_property.call(test2, "user_1" ); //Bună ziua, Petya.


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

De asemenea, ar trebui să fie clar din exemplu că nu există pași clari pentru crearea și utilizarea unui obiect. Un obiect poate fi modificat în orice mod în orice moment - înainte, după și chiar în timpul utilizării. Aceasta este, de asemenea, o diferență importantă față de OOP „tradițional”.

Constructor

În exemplul de mai sus, am creat 2 obiecte care au avut unele similarități. Ambele aveau proprietăți simple_property și object_property. Evident, atunci când scrieți cod real, apare adesea sarcina de a crea obiecte identice sau pur și simplu similare. Și, desigur, nu trebuie să creăm manual fiecare astfel de obiect.

Un designer ne va veni în ajutor. Un constructor în JavaScript nu face parte dintr-o clasă (pentru că nu există clase), ci pur și simplu o funcție în sine. Cea mai comună funcție.

make_me= functie (_nume) (
alert("Am fost lansat");
acest .nume=_nume;

}


/* Să ne dăm seama ce se întâmplă aici. Interpretul vede noul operator și verifică ce este în dreapta acestuia. Deoarece make_me este o funcție și poate fi folosită ca constructor, apoi un nou obiect este creat în memorie și funcția make_me este lansată pentru execuție, iar acest lucru indică exact acest nou obiect. Apoi, acest obiect este adăugat cu o proprietate name, căruia i se atribuie valoarea din argumentul _name și o metodă show_name. De asemenea (nu știu în ce moment, dar nu contează) variabila copil începe să indice către obiectul nostru nou-nouț, tocmai născut */

Alerta(nume.copil); //Vasya
child.show_name(); //Vasya


child2.show_name(); //Petru

Child2.show_name=funcție () (alertă( „Nu îmi voi spune numele”);} //Nu uitați că ne putem schimba obiectele în orice moment
child2.show_name(); //Nu îmi voi spune numele

Child.show_name(); //Vasya - copiii nu se influențează între ei în niciun fel


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

De asemenea, puteți compara un designer cu un tată - el dă naștere unui copil, înzestrându-l cu anumite calități, dar imediat după creare copilul devine complet independent de părinte și poate deveni foarte diferit de frații săi.
Dacă ne amintim descrierea tipurilor de date de la începutul articolului, devine clar că Object și subtipurile sale (Funcție, Matrice și altele) sunt de fapt constructori care conferă obiectului creat capabilitățile unei funcții, matrice etc.

Deci asta este deja mult mai bine. Acum avem capacitatea de a crea obiecte după un model. Totuși, încă nu este totul bine. În primul rând, fiecare obiect pe care îl creăm și toate proprietățile și metodele sale ocupă un loc separat în memorie, deși în multe feluri sunt repetate. În al doilea rând, dacă vrem să menținem legătura dintre părinte și copil și să putem schimba toate obiectele copil simultan. Un prototip ne va veni în ajutor.

Prototip

Așa cum fiecare copil are un tată și o mamă (cel puțin în sens biologic), la fel și fiecare obiect din JavaScript. Și dacă tatăl, așa cum am stabilit, lucrează ca designer, atunci mama este tocmai prototipul. Să vedem cum se întâmplă asta:
make_me= functie (_nume) (
alert("Am fost lansat");
acest .nume=_nume;
acest .show_name=funcție () (alertă (acest .nume);)
}
/*
Văzând cuvântul cheie al funcției, interpretul verifică codul din dreapta acestuia și așa mai departe. totul este ok - creează un nou obiect în memorie, care este și funcția noastră. Apoi, automat (fără intervenția programatorului) este creată o proprietate prototip pentru această funcție, care se referă la un obiect gol. Dacă am face acest lucru manual, ar arăta ca make_me.prototype=new Object();

Apoi, acestui obiect (indicat de proprietatea prototip) i se dă automat și o proprietate de constructor, indicând înapoi la funcție. Se pare că aceasta este o legătură ciclică.

Acum acest obiect, care poate fi descris ca (constructor: ... aici este o referință la o funcție...) este prototipul funcției.
*/

//Obiect - într-adevăr, un obiect
alert(typeof make_me.prototype.constructor); //Funcția este funcția noastră
alert(make_me.prototype.constructor === make_me); //Adevărat

//Adăugați o nouă metodă la prototipul funcției make_me

Copil=new make_me ("Vasya"); //Am fost lansat
/* Acum, pe lângă tot ceea ce este descris în exemplul anterior, o proprietate ascunsă suplimentară [] este creată în obiectul copil, care indică același obiect ca make_me.prototype. Deoarece Proprietatea este ascunsă, nu putem nici să-i vedem valoarea, nici să o schimbăm - cu toate acestea, joacă un rol important în lucrările ulterioare */

Alerta(nume.copil); //Vasya
child.show_name(); //Vasya

Child.set_name("Kolya");
/* În primul rând, interpretul caută metoda set_name în obiectul copil. Deoarece nu este acolo, continuă să caute în proprietatea child.[, o găsește acolo și o rulează. */
child.show_name(); //Kolya - acum numele lui Vasya este Kolya :)

Make_me.prototype.show_name2=funcție () (alertă ("Bună ziua, " + acest .nume;) //Deoarece un prototip este un obiect obișnuit, la fel de bine îl putem schimba din mers

Copil2=new make_me("Petya" );
copil2.show_name2(); //Bună, Petya
copil.show_name2(); //Bună, Kolya - modificările în prototip afectează nu numai obiectele nou create, ci și pe toate cele vechi

Child2.show_name2=funcție () (alertă( „Nu îmi voi spune numele”);} //Putem încă schimba obiectul în sine, iar noua metodă show_name2 din acest obiect (și numai în el) va „suprascrie” vechea metodă din prototip
copil2.show_name2(); //Nu îmi voi spune numele - pentru că... acum avem propria noastră metodă show_name2, apoi este numită, iar căutarea în prototip nu are loc

Child.show_name2(); //Bună ziua, Kolya - totul este la fel aici

Make_me.prototype=(prop: „bună ziua”) //Să încercăm să recreăm prototipul din nou

Alertă(copil.prop); //nedefinit
copil.show_name2(); //Bună Kolya
/* Dacă vă amintiți ce este lucrul prin referință, atunci totul este clar. Recrearea prototipului întrerupe conexiunea, iar acum proprietatea [] a obiectelor copil și child2 indică către un obiect (care era anterior prototipul funcției make_me), iar proprietatea make_me.prototype indică către un alt obiect, care este noul obiect. prototipul funcției make_me */

Child3=new make_me("Oleg" );
alert(copil3.prop); //bună ziua - așa cum era de așteptat


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

După cum se vede din exemplu, atâta timp cât tatăl rămâne fidel mamei (adică atât timp cât tipul de funcție rămâne același), toți copiii depind de mamă și sunt sensibili la toate schimbările din ea. Totuși, de îndată ce părinții divorțează (designerul schimbă prototipul cu altul), copiii fug imediat în toate direcțiile și nu mai există contact cu ei.

Un pic despre terminologie
Atâta timp cât legătura primară dintre proiectant și prototip nu este întreruptă, putem observa următoarea imagine:

make_me= functie (_nume) (
alert("Am fost lansat");
acest .nume=_nume;
acest .show_name=funcție () (alertă (acest .nume);)
}

Make_me.prototype.set_name=funcție (_name) (acest .name=_name;)
copil=new make_me("Vasya" );

Alertă (tip de make_me.prototype); //obiect - funcția are o proprietate prototip
alert(tip de copil.prototip); //nedefinit - obiectul creat NU are o proprietate prototip
alert(child.constructor.prototype === make_me.prototype); //true - dar obiectul are o proprietate de constructor, care indică funcția de constructor make_me, care, la rândul său, are o proprietate de prototip


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

După cum am observat după ce am citit numeroase forumuri pe acest subiect, principala problemă pe care o au oamenii este atunci când confundă proprietatea prototip a unei funcții cu proprietatea ascunsă [] a obiectului creat de acea funcție.
Ambele proprietăți sunt o referință la același obiect (atâta timp cât conexiunea primară dintre prototip și constructor nu este întreruptă), dar sunt totuși proprietăți diferite, cu nume diferite, dintre care unul este accesibil programatorului și celălalt nu este.

Este întotdeauna necesar să înțelegem clar că, dacă vorbim despre prototipul constructorului, atunci aceasta este întotdeauna proprietatea prototipului, iar dacă vorbim despre prototipul obiectului creat, atunci aceasta este proprietatea ascunsă [].

Moştenire

Acum știm că fiecare obiect are o referință de prototip ascunsă și fiecare prototip este un obiect obișnuit.
Cei mai sensibili cititori au prins deja mirosul recursiunii :)
Într-adevăr, pentru că un prototip este un obiect obișnuit, apoi el, la rândul său, are o legătură cu prototipul său și așa mai departe. Acesta este modul în care este implementată ierarhia prototipului.
pasăre= funcţie()() //Acesta este constructorul păsării
bird.prototype.cry=funcție ()(alertă(„Plânge!”);) //Pasarea poate striga
bird.prototype.fly=funcție ()(alertă(„zboară!”);) //și zboară

Duck=funcție () ()
duck.prototype=pasare noua();
duck.prototype.cry=funcție ()(alertă(„Cărlatan!” ;) //Rața țipă altfel
duck.prototype.constructor=rata; //Setați forțat proprietatea prototype.constructor la rață, deoarece altfel se va referi la pasăre

Billy = rata noua(); //Billy este rata noastră
billy.fly(); //Eu zbor! - Billy poate zbura pentru că este o pasăre.
billy.cry(); //Cărlatan, şarlatan! - Billy țipă șarlatan pentru că e o rață.


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

În acest fel, puteți implementa o ierarhie a oricărui nivel de imbricare.

Sarcina vedetă

Acum, din moment ce știm atât de multe despre toate acestea, să încercăm să ne dăm seama cât de multe se întâmplă în aceste trei rânduri
make_me= funcţie()()
copil=nou make_me();
alert(child.toString()); //ieșiri

* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

Pe prima linie creăm o nouă funcție și o variabilă numită make_me care indică acea funcție. Aceasta creează un prototip de funcție, make_me.prototype, care conține o proprietate de constructor care indică către make_me.
Dar asta nu este tot:)
Deoarece funcția make_me este, de asemenea, un obiect, apoi are, la rândul său, un tată și o mamă, i.e. designer și prototip. Constructorul său este o funcție nativă a limbajului Function(), iar prototipul său este un obiect care conține metodele call, apply etc. - Datorită acestui prototip putem folosi aceste metode în orice funcție. Astfel, funcția make_me are o proprietate [] care indică Function.prototype.

La rândul său, prototipul unui constructor Function este, de asemenea, un obiect, al cărui constructor este (surpriză!) Object (adică Function.prototype.[].constructor===Object), iar prototipul este un obiect care conține proprietățile standard. și metodele obiectului.cum ar fi toString, hasOwnProperty și altele (cu alte cuvinte - Function.prototype.[]["hasOwnProperty"] - aceasta este exact metoda pe care o putem folosi în toate obiectele derivate - și aceasta este metoda proprie a acestui obiect, și nu unul moștenit). În acest mod interesant descoperim că tot felul de obiecte sunt derivate din Object.

Putem continua mai departe? Se dovedește că nu. Object.prototype conține proprietățile de bază ale obiectului tocmai pentru că nu are propriul prototip. Object.prototype.[]=null; În acest moment, călătoria prin lanțul de prototipuri în căutarea unei proprietăți sau a unei metode se oprește.

Un alt fapt interesant este că constructorul Object este Function. Acestea. Object.[].constructor===Funcție.
Există o altă referință circulară - constructorul Object este Function, iar constructorul Function.prototype este Object.

Să revenim la exemplul nostru. Am înțeles deja cum este creată funcția, acum să trecem la a doua linie. Acolo creăm un obiect copil al cărui constructor este funcția make_me și al cărui prototip este make_me.prototype.

Ei bine, în a treia linie vedem cum interpretul urcă în lanț, de la copil la copil.[] (aka make_me.prototype), apoi la copil.[].[] (aka Object.prototype), și găsește deja acolo metoda toString, care lansează execuția.

Impurităţi

Poate părea că moștenirea prin prototipuri este singura modalitate posibilă în JavaScript. Este gresit.
Avem de-a face cu un limbaj foarte flexibil care oferă mai degrabă posibilități decât reguli.

De exemplu, dacă dorim, nu putem folosi prototipuri deloc, ci programăm folosind conceptul de mixin. Pentru asta vom avea nevoie de vechii noștri prieteni buni - designeri.

//Acesta este un constructor uman
om=funcție() (
this .live=function ()(alerta ("Sunt live" ;) //Omul știe să trăiască
acest .walk=funcție ()(alertă(„Mă plimb” ;) //Omul poate merge
}

//Acesta este constructorul poetului
poet=funcție ()(
aceasta .kill=funcție ()(alertă( „Poetul a ucis un om”);} //Un poet poate ucide o persoană
acest .live=funcție ()(alertă(„Sunt mort” ;) //O persoană va muri din asta
}

Vladimir=om nou(); //Vladimir este bărbat
vladimir.live(); // Eu trăiesc - el este în viață
vladimir.walk(); //I'm walking - el merge

Poet.call(vladimir); //Execută poetul constructor pentru obiectul vladimir
vladimir.kill(); //Poetul a ucis un om
vladimir.live(); //Sunt mort

//Acum concentrează-te
man.call(vladimir);
vladimir.live(); //Eu traiesc


* Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

Ce vedem în acest exemplu? În primul rând, este posibil să se moștenească de la mai multe obiecte care nu se află în aceeași ierarhie. În exemplu sunt 2, dar pot fi câte doriți.
În al doilea rând, nu există deloc ierarhie. Suprascrierea proprietăților și metodelor este determinată numai de ordinea în care sunt apelați constructorii.
În al treilea rând, aceasta este capacitatea de a schimba un obiect și mai dinamic, în special un obiect individual, și nu toți descendenții săi, ca atunci când se schimbă un prototip.

Actualizare: Închideri și proprietăți private

Pentru a nu umfla acest articol deja destul de mare, pun un link către postarea Închideri în JavaScript, unde este scris în detaliu.

Ce să faci acum cu toate astea

După cum am spus mai sus, modificarea arbitrară a obiectelor individuale, utilizarea constructorilor, mixin-urilor și flexibilitatea prototipurilor sunt doar instrumente, oportunități care permit programatorului să creeze atât cod groaznic, cât și minunat în toate privințele. Este important doar să înțelegem ce probleme rezolvăm, prin ce mijloace, ce obiective atingem și ce preț plătim pentru asta.

Mai mult decât atât, problema prețului este destul de nebanală, mai ales dacă vorbim de dezvoltare pentru versiunile de browser Internet Explorer 6 și 7.
1. Memorie - totul este simplu aici. În toate browserele, moștenirea pe prototipuri ocupă mult mai puțină memorie decât atunci când se creează metode prin constructori. Mai mult, cu cât avem mai multe metode și proprietăți, cu atât diferența este mai mare. Cu toate acestea, merită să ne amintim că, dacă nu avem o mie de obiecte identice, ci doar unul, atunci consumul de memorie va fi în orice caz mic, deoarece Există și alți factori de luat în considerare aici.
2. Timpul procesorului – aici principalele subtilități sunt legate în mod specific de browserele de la Microsoft.
Pe de o parte, obiectele în care metodele și proprietățile sunt create printr-un constructor pot fi create de multe ori (în unele cazuri de zeci și sute de ori) mai încet decât printr-un prototip. Cu cât sunt mai multe metode, cu atât este mai lent. Deci, dacă IE-ul tău se blochează pentru câteva secunde în timpul inițializării scriptului, există un motiv pentru a săpa în această direcție.

Pe de altă parte, metodele proprii ale unui obiect (cele create prin constructor) se pot executa puțin mai rapid decât metodele prototip. Dacă aveți nevoie disperată de a accelera execuția unei anumite metode în acest browser, atunci trebuie să țineți cont de acest lucru. Rețineți că apelul metodei (adică căutarea acestuia în obiect) este accelerat, nu execuția sa. Deci, dacă metoda în sine rulează pentru o secundă, atunci nu veți observa o creștere mare a performanței.

Probleme similare sunt observate în alte browsere, unde timpul pentru crearea obiectelor și apelarea metodelor acestora este aproximativ același pentru ambele abordări.

P.S. De obicei, în articole de acest fel, autorul oferă un fel de înveliș, fie încercând să implementeze moștenirea de clasă-obiect bazată pe moștenirea prototipică, fie pur și simplu zahăr sintactic pentru moștenirea prototipică. Nu fac asta în mod intenționat, pentru că... Cred că o persoană care înțelege semnificația acestui articol este capabilă să scrie orice pachet pentru sine și multe alte lucruri interesante :)

Etichete: Adăugați etichete