This translation is incomplete. Please help translate this article from English.

Prototipul (prototype) este mecanismul prin care obiecte JavaScript își transmit între ele proprietăți, diferit de mecanismul de moștenire din limbajele clasice de programare orientate pe obiect. În acest articol examinăm această diferență, explicăm cum funcționează succesiunile prototipurilor și aflăm cum se folosește proprietatea prototype pentru a adăuga metode la constructori existenți.

Preliminare: Cunoștințe de bază despre computer, înțelegerea elementară a HTML și a CSS, deprinderea conceptelor JavaScript (vezi First steps și Building blocks) și ale OOJS (vezi Introduction to objects).
Obiectiv: Înțelegerea prototipurilor obiectelor JavaScript, a înlănţuirii prototipurilor și adăugarea de metode proprietății prototipului.

Un  limbaj bazat pe prototip?

De multe ori, JavaScript este descris ca prototype-based language — fiecare obiect are un obiect prototip, care se comportă ca un obiect-șablon de la care se moștenesc metode și proprietăți. La rândul său, un prototip poate avea un alt obiect prototip de la care moşteneşte metode şi proprietăţi, şamd. Aceasta este frecvent denumită prototype chain, şi explică de ce obiecte diferite au proprietăţi şi metode definite de alte obiecte accesibile lor.

Mai precis, proprietăţile şi metodele sunt definite de proprietatea prototype a funcţiilor constructor ale Obiectului, nu de instanţele propriu-zise ale obiectului.

În OOP clasică, clasele sunt definte, iar când mai apoi instanțele obiectului sunt create, toate proprietățile și metodele definite în clasă sunt transmise instanței. În JavaScript, ele nu sunt transmise — în schimb, este creată o legătură între instanța obiectului și prototipul său (proprietatea __proto__, care derivă din proprietatea prototype a constructorului), iar proprietățile și metodele sunt găsite urcând pe lanțul prototipurilor.

Notă: Este important să înțelegem distincția dintre prototipul unui obiect (accesibil prin Object.getPrototypeOf(obj), sau prin proprietatea depreciată __proto__) și proprietatea prototype a funcțiilor constructor. Cea dintâi este proprietatea fiecărei instanțe, iar cealaltă este proprietatea constructorului. De aceea, Object.getPrototypeOf(new Foobar()) se referă la același obiect caFoobar.prototype.

Un exemplu va clarifica mai bine.

Înțelegerea obiectelor prototype

Să ne întoarcem la exemplul în care am finalizat de scris constructorul Person() — încărcăm exemplul în browser. Dacă nu-l mai avem din articolul precedent, folosim exemplul oojs-class-further-exercises.html (vezi și source code).

Aici am definit o funcţie constructor, astfel:

function Person(first, last, age, gender, interests) {
  
  // definiții de proprietăți și metode
  
}

După care am creat o instanţă a obiectului:

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

Dacă scriem "person1." în consola JavaScript, observăm cum browserul încearcă să auto-completeze cu numele membrilor disponibili pentru acest obiect:

Observăm în această listă membrii definiți de obiectul prototip person1, care este constructorul Person() — name, age, gender, interests, bio, and greeting. Remarcăm de asemenea și alți membri — watch, valueOf, etc — definiți de obiectul prototip al constructorului Person(), care este Object. Iată demonstrarea funcţionării lanţului prototype.

Aşadar, ce se întâmplă dacă apelezi o metodă a person1, definită de Object? De exemplu:

person1.valueOf()

Metoda aceasta returnează doar valoarea obiectului apelat — probează! Ce se întâmplă în această situație:

  • Browserul verifică mai întâi dacă obiectul person1 are o metodă valueOf() disponibilă.
  • Nu are, așa că browserul verifică dacă obiectul prototip al obiectului person1 (prototipul constructorului Person()) are metoda valueOf() disponibilă pentru el.
  • Nici pe aceasta nu o are, astfel că browserul inspectează dacă obiectul prototip al obiectului prototip al constructorului Person() (Object() constructor's prototype) are o metodă valueOf() utilizabilă. Are, o apelează și s-a rezolvat!

Notă: Insistăm şi repetăm: metodele și proprietățile nu sunt copiate de la un obiect la altul în prototype chain — ci sunt accesate parcurgând lanțul, ca în descrierea de mai sus.

Notă: Nu există o cale formală de a accesa obiectul prototip al unui obiect în mod direct — „legăturile” dintre elementele lanţului sunt definite de o proprietate internă, numită [[prototype]] în specificaţiile limbajului JavaScript (vezi ECMAScript). Majoritatea browserelor moderne au totuși o proprietate disponibilă pentru ele __proto__ (sunt câte două underscore în fiecare parte), care include obiectul prototip al obiectului. De exemplu, probează person1.__proto__ și person1.__proto__.__proto__ ca să vezi codul conținutului lanțului!

Începând cu ECMAScript 2015 se poate accesa un objects prototype object indirect, prin Object.getPrototypeOf(obj).

Proprietatea prototype: Unde sunt definiți membrii moșteniți?

Așadar, unde sunt definite proprietățile și metodele moștenite? Dacă ne uităm la pagina de referință Object, observăm în partea stângă un mare număr de proprietăți și metode — mult mai multe decât numărul membrilor moșteniți pe care i-am remarcat, în captura de mai sus, accesibili pentru obiectul person1. Unii sunt moșteniți, dar nu toți — de ce se întâmplă asta?

Fiindcă cei moșteniți sunt definiți de proprietatea prototype (o poți gândi ca pe un sub-namespace) — adică cei care încep cu Object.prototype., nu și cei care încep doar cu Object. Valoarea proprietății prototype este un obiect, care în principiu este un recipient de stocare a proprietăților și metodelor pe care le dorim transmise prin moștenire obiectelor de-a lungul prototype chain.

Așadar, Object.prototype.watch(), Object.prototype.valueOf(), etc., sunt accesibile oricăror tipuri de obiecte care derivă din Object.prototype, inclusiv noilor instanţe obiect create de constructor.

Object.is(), Object.keys() şi alţi membri care nu sunt definiţi în interiorul recipientului prototype nu sunt moşteniţi de instanţele obiect sau de tipurile obiect care succed Object.prototype. Ei sunt doar metode/proprietăţi accesibile constructorului Object() însuşi.

Notă: Pare neobișnuit — cum poţi avea o metodă definită de un constructor, care este la rândul său o funcţie? Ei bine, o funcţie este tot un tip de obiect — vezi constructorul Function() ca să te convingi.

  1. Poți verifica tu însuți proprietățile prototype — întoarce-te la exemplul anterior și introdu aceasta în consola JavaScript:
    Person.prototype
  2. Rezultatul nu relevă prea multe — în definitiv, n-am definit nimic cu prototipul constructorului nostru! În mod implicit, un prototype al constructorului nu conține nimic. Introdu aceasta acum:
    Object.prototype

Observăm un număr mare de metode definite de proprietatea prototype a Object, care vor fi disponibile pentru orice obiect moștenitor al lui Object, așa cum am descris mai sus.

Remarcăm exemple de moștenire în lanțul prototipurilor oriunde în JavaScript — caută de exemplu metode și proprietăți definite de prototype pentru obiectele globale String, Date, Number şi Array. Toate au un număr de membri definiţi de prototipul lor şi acesta e motivul pentru care putem crea un şir, de exemplu:

var myString = 'This is my string.';

myString are în mod implicit o serie de metode pe care le poate accesa, cum ar fi split(), indexOf(), replace() etc.

Important: Denumirea proprietății prototype este una din părțile cele mai neclare ale JavaScript — ești tentat să consideri că this trimite la obiectul prototip al obiectului curent, dar nu este așa (ne amintim că există un obiect intern care poate fi accesat de __proto__). În schimb, prototype este o proprietate care conține un obiect căruia îi definim membri pe care-i vrem transmiși prin moștenire.

Recapitulând create()

Am arătat mai devreme cum folosim metoda Object.create() pentru a crea o nouă instanță obiect.

  1. Introdu de exemplu aceasta în consola JavaScript precedentă:
    var person2 = Object.create(person1);
  2. De fapt, create() realizează un nou obiect pornind de la un prototip de obiect specificat. Aici, person2 este creat utilizând person1 ca obiect prototip. Asta se poate verifica introducând în consolă următoarele:
    person2.__proto__

Ceea ce returnează obiectul person1.

Proprietatea constructor

Fiecare funcție constructor are o proprietate prototip a cărei valoare este un obiect ce conține o proprietate constructor. Această proprietate constructor trimite la funcția constructor originală. După cum vom vedea în secțiunea următoare, proprietățile definite de proprietatea Person.prototype (în general de proprietatea prototip a funcției constructor, care este un obiect, chestiune menționată în secțiunea precedentă) devin disponibile tuturor instanțelor obiect create prin constructorul Person(). Așadar, proprietatea constructor este utilizabilă de ambele obiecte person1 și person2.

  1. Introduceți aceste comenzi în consolă:
    person1.constructor
    person2.constructor

    Ambele returnează constructorul Person(), care conține definiția originală a acestor instanțe.

    Este o bună idee să punem parenteze după proprietatea constructor (unde includem parametri) pentru a crea o altă instanță obiect a acelui constructor. În fond, un constructor este o funcție, așa încât poate fi apelat utilizând parenteze; trebuie doar să includem cuvântul-cheie new pentru a indica că dorim folosirea funcțion ca un constructor.

  2. Tastați în consolă:
    var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
  3. Apoi accesăm caracteristicile noului obiect, de exemplu:
    person3.name.first
    person3.age
    person3.bio()

Funcționează. Nu prea avem nevoie de asta, însă poate fi util pentru a crea o nouă instanță și nu avem, dintr-un motiv oarecare, acces facil la constructorul original.

Proprietatea constructor are utilizări colaterale. Spre exemplu, pentru a obține numele constructorului a cărei instanță este un obiect, folosim:

instanceName.constructor.name

Ca de exemplu:

person1.constructor.name

Notă: Valoarea constructor.name se poate modifica (due to prototypical inheritance, binding, preprocessors, transpilers, etc.), deci vom utiliza în schimb instanceof pentru operații mai complexe. 

Modificarea prototipurilor

Let's have a look at an example of modifying the prototype property of a constructor function (methods added to the prototype are then available on all object instances created from the constructor).

  1. Go back to our oojs-class-further-exercises.html example and make a local copy of the source code. Below the existing JavaScript, add the following code, which adds a new method to the constructor's prototype property:
    Person.prototype.farewell = function() {
      alert(this.name.first + ' has left the building. Bye for now!');
    };
  2. Save the code and load the page in the browser, and try entering the following into the text input:
    person1.farewell();

You should get an alert message displayed, featuring the person's name as defined inside the constructor. This is really useful, but what is even more useful is that the whole inheritance chain has updated dynamically, automatically making this new method available on all object instances derived from the constructor.

Think about this for a moment. In our code we define the constructor, then we create an instance object from the constructor, then we add a new method to the constructor's prototype:

function Person(first, last, age, gender, interests) {

  // property and method definitions

}

var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
};

But the farewell() method is still available on the person1 object instance — its available functionality has been automatically updated. This proves what we said earlier about the prototype chain, and the browser looking upwards in the chain to find methods that aren't defined on the object instance itself rather than those methods being copied to the instance. This provides a very powerful, extensible system of functionality.

Notă: If you are having trouble getting this example to work, have a look at our oojs-class-prototype.html example (see it running live also).

You will rarely see properties defined on the prototype property, because they are not very flexible when defined like this. For example you could add a property like so:

Person.prototype.fullName = 'Bob Smith';

But this isn't very flexible, as the person might not be called that. It'd be much better to do this, to build the fullName out of name.first and name.last:

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

This however doesn't work, as this will be referencing the global scope in this case, not the function scope. Calling this property would return undefined undefined. This worked fine on the method we defined earlier in the prototype because it is sitting inside a function scope, which will be transferred successfully to the object instance scope. So you might define constant properties on the prototype (i.e. ones that never need to change), but generally it works better to define properties inside the constructor.

In fact, a fairly common pattern for more object definitions is to define the properties inside the constructor, and the methods on the prototype. This makes the code easier to read, as the constructor only contains the property definitions, and the methods are split off into separate blocks. For example:

// Constructor with property definitions

function Test(a, b, c, d) {
  // property definitions
}

// First method definition

Test.prototype.x = function() { ... };

// Second method definition

Test.prototype.y = function() { ... };

// etc.

This pattern can be seen in action in Piotr Zalewa's school plan app example.

Sumar

Acest articol a tratat prototipuri obiect în JavaScript, inclusiv transmiterea prin moștenire a proprietăților obiectelor prin lanțul obiectelor prototype, adăugarea de metode constructorilor cu proprietatea prototype și alte teme asociate.

În articolul următor vom trata implementarea moștenirii funcționalității între două obiecte particulare.

 

În acest modul

 

Document Tags and Contributors

Contributors to this page: cicotan
Last updated by: cicotan,