MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Extinderea obiectelor - Prototipuri

JavaScript este un limbaj bazat pe prototipuri, ceea ce înseamnă că nu cunoaște nicio clasă reală - cel puțin, nu în sensul tradițional. În schimb, totul în JavaScript este bazat pe obiecte. Aproape fiecare obiect în JavaScript se bazează pe un prototip.

Excepțiile, cum ar fi tipul Object (baza tuturor obiectelor) sau obiectele ale căror prototipuri sunt explicit setate la null, nu au prototip. Fiecare obiect poate servi și ca șablon, adică prototip, pentru alt obiect. În acest caz, noul obiect moștenește proprietățile și metodele prototipului.

Prototipul unui obiect este stocat în proprietatea __proto__, implementată ca pseudonim pentru proprietățile interne Prototype. De asemenea, se poate obține prototipul unui obiect folosind metoda getPrototypeOf(). De exemplu:

const tom = {name: "Tom", age: 39};

// obținem prototipul
console.log(tom.__proto__);                 // Object
console.log(Object.getPrototypeOf(tom));    // Object

În ambele cazuri, vom obține același rezultat, sub forma definiției tipului Object.

Object
    constructor: ƒ Object()
    hasOwnProperty: ƒ hasOwnProperty()
    isPrototypeOf: f isPrototypeOf()
    propertyIsEnumerable: f propertyIsEnumerable()
    toLocaleString: f toLocaleString()
    toString: f toString()
    valueOf: f valueOf()
    __defineGetter__: f __defineGetter__()
    __defineSetter__: f __defineSetter__()
    __lookupGetter__: f __lookupGetter__()
    __lookupSetter__: f __lookupSetter__()
    __proto__: null
    get __proto__: f __proto__()
    set __proto__: f __proto__(

Prototipul Funcțiilor Constructor

În tema anterioară am discutat despre funcțiile constructor, cunoscute și sub numele de constructori de obiecte, care permit definirea unui tip de obiect și crearea de instanțe ale acestui tip de obiect.

Fiecare astfel de funcție constructor definește propriul său prototip, care servește ca bază pentru obiectele create. Acest prototip poate fi, de asemenea, obținut folosind proprietatea prototype. De exemplu:

function Person(name, age) {
   this.name = name;
   this.age = age;
   this.print = function(){
       console.log(`Name: ${this.name}  Age: ${this.age}`);
   };
}

const tom = new Person("Tom", 39);

//primim prototipul
console.log(Person.prototype);
console.log(tom.__proto__);
console.log(Object.getPrototypeOf(tom));

Aici obținem prototipul funcției constructor pentru Person. Toate cele trei modalități de obținere a prototipului sunt echivalente, iar la afișarea în consolă în toate cele trei cazuri vom vedea ceva similar:

{constructor: ƒ} constructor : ƒ Person(name, age) Prototype : Object

Constructorul și prototipul

Este important să facem distincția între constructor și prototip. Prototipul este, în esență, un plan al obiectului, care poate consta din diverse părți - metode și variabile, în timp ce constructorul în sine reprezintă doar o parte a prototipului. De exemplu, să luăm funcția Person definită mai sus:

function Person(name, age) {
   this.name = name;
   this.age = age;
   this.print = function(){
       console.log(`Name: ${this.name}  Age: ${this.age}`);
   };
}
console.log(Person.prototype);

Afișaj în consolă:

{constructor: ƒ}
        constructor: ƒ Person(name, age)
        Prototype: Object

Schematic, putem reprezenta prototipul astfel:

În realitate, prototipul funcției constructor pentru Person constă doar din constructor (în care implicit sunt incluse și metodele moștenite de la tipul Object, cum ar fi toString()). Putem obține acest constructor folosind proprietatea constructor:

console.log(Person.prototype.constructor);

Consola ar trebui să afișeze ceva similar cu:

ƒ Person(name, age) {
        this.name = name;
        this.age = age;
        this.print = function(){
                console.log(Name: ${this.name}  Age: ${this.age});
        };

Deoarece proprietatea constructor face parte din prototip, puteți să o accesați și prin intermediul numelui obiectului:

const tom = new Person("Tom", 39);
console.log(tom.constructor);

Acum vom elimina metoda print() din constructor și o vom defini ca parte a prototipului:

function Person (name, age) {
   this.name = name;
   this.age = age;
}

// funcția print este definită ca parte a prototipului
Person.prototype.print = function(){
   console.log(`Name: ${this.name}  Age: ${this.age}`);
};

console.log(Person.prototype);

Afișajul în consolă al browser-ului:

{print: ƒ, constructor: ƒ}
        print: ƒ ()
        constructor: ƒ Person(name, age)
        Prototype: Object

Acum, prototipul constă din funcția print și constructor:

Indiferent de modul în care definim metodele și proprietățile - în interiorul constructorului sau ca parte a prototipului, le putem utiliza în același mod pentru obiectele de acest tip:

function Person(name, age) {
   this.name = name;
   this.age = age;
   this.print = function(){
       console.log(`Name: ${this.name}  Age: ${this.age}`);
   };
}

const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);

// modificăm prototipul
Person.prototype.sayHello = function(){
   console.log(this.name, "says: Hello");
};

tom.print();    // Name: Tom  Age: 39
tom.sayHello(); // Tom says: Hello
bob.print();    // Name: Bob  Age: 43
bob.sayHello(); // Bob says: Hello

În acest caz, putem defini aceleași proprietăți și metode atât în interiorul constructorului, cât și ca parte a prototipului:

// constructorul pentru utilizatori
function Person (name, age) {
   this.name = name;
   this.age = age;
   this.print = function(){
       console.log(`[Constructor] Nume: ${this.name}  Vârstă: ${this.age}`);
   };
}

Person.prototype.print = function(){
   console.log(`[Prototip] Nume: ${this.name}  Vârstă: ${this.age}`);
};

const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);
tom.print();    // [Constructor] Nume: Tom  Vârstă: 39
bob.print();    // [Constructor] Nume: Bob  Vârstă: 43

În acest caz, metodele definite în interiorul constructorului vor ascunde metodele cu același nume din prototip.

Definirea proprietăților prototipului

În mod similar, se pot adăuga și proprietăți. De exemplu, să adăugăm proprietatea company, care reprezintă compania:

const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);

// adăugăm în prototipul proprietatea company
Person.prototype.company = "SuperCorp";
console.log(tom.company);   // SuperCorp
console.log(bob.company);   // SuperCorp

Este important să observăm că valoarea proprietății company va fi aceeași pentru toate obiectele, fiind o proprietate statică partajată. Spre deosebire, de exemplu, de proprietatea this.name, care stochează o valoare specifică pentru un anumit obiect.

Totodată, putem defini într-un obiect o proprietate care să aibă același nume cu o proprietate a prototipului. În acest caz, proprietatea specifică obiectului va avea prioritate față de proprietatea prototipului:

const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);

Person.prototype.company = "SuperCorp";
bob.company = "MegaCorp";   // definim proprietatea cu același nume la nivelul unui obiect
console.log(bob.company);   // MegaCorp - ia proprietatea din obiectul bob
console.log(tom.company);   // SuperCorp - ia proprietatea din prototipul Person

La accesarea proprietății company, JavaScript caută mai întâi această proprietate printre proprietățile obiectului, iar dacă nu este găsită, atunci se referă la proprietățile prototipului. Același principiu se aplică și la metode.

← Lecția anterioară Lecția următoare →