Skip to main content

Els Object

En ES5 (abans de la sintaxi class), un objecte (en el sentit d'una "instància" d'un "plànol") és el resultat de cridar una funció constructora utilitzant la paraula clau new. ES5 (ECMAScript 5) es va publicar l'any 2009 i va ser l'estàndard de JavaScript durant molts anys fins a l'arribada d'ES6 (ES2015) el 2015.

Aquest objecte és una col·lecció de propietats que té dues parts:

  1. Propietats Pròpies (Own Properties): Les dades úniques d'aquesta instància.

  2. Un Enllaç a un Prototip: Un enllaç intern a un objecte compartit (el prototype del constructor) on resideixen tots els mètodes.

Abans d'ES6, no teníem la paraula clau class. Tot es feia amb funcions i un objecte especial anomenat prototype.

Un objecte ES5

Anem a descriure l'objecte usuari1 creat a partir d'un constructor Usuari.

Definició

Primer, necessitem la definició (el que ara anomenem "classe"):

JavaScript
// 1. LA FUNCIÓ CONSTRUCTORA
// (Defineix les propietats PRÒPIES que tindrà l'objecte)
function Usuari(nom, edat) {
  // 'this' es refereix al nou objecte que s'està creant
  this.nom = nom;
  this.edat = edat;
}

// 2. EL PROTOTIP
// (Defineix els mètodes COMPARTITS)
Usuari.prototype.saludar = function() {
  console.log("Hola, sóc " + this.nom);
};

Instanciació

Ara creem l'objecte:

JavaScript
let usuari1 = new Usuari("Anna", 30);

 

Objecte resultant (usuari1)

L'objecte usuari1 que s'acaba de crear NO conté el mètode saludar. Si miressis usuari1 per dins, veuriem això:

JavaScript
{
  // 1. PROPIETATS PRÒPIES
  // Aquestes propietats existeixen FÍSICAMENT dins de 'usuari1'
  nom: "Anna",
  edat: 30,
  
  // 2. ENLLAÇ AL PROTOTIP (Amagat)
  // Això és un enllaç intern, no una propietat visible directament.
  // Tots els navegadors antics ho exposaven com '__proto__'
  __proto__: Usuari.prototype
}

Mentre que Usuari.prototype és un objecte separat que conté:

JavaScript
{
  saludar: function() { ... },
  constructor: Usuari // (Una referència de tornada al constructor)
}

Com funciona? Cadena de prototips

Quan tu crides a usuari1.saludar(), passa el següent:

  1. JavaScript busca la propietat saludar dins de l'objecte usuari1.

  2. No la troba (allà només hi ha nom i edat).

  3. Com que no la troba, segueix l'enllaç intern (__proto__) i va a buscar a Usuari.prototype.

  4. Allà sí que troba saludar!

  5. Executa la funció saludar establint el this perquè apunti a usuari1.

En resum, un objecte ES5 és un contenidor de dades úniques amb un enllaç a un prototip que conté els mètodes compartits. Com es feia cada part:

El "Constructor"

En lloc d'una class amb un mètode constructor(), simplement creàvem una funció normal. Per convenció, la posàvem amb la primera lletra en majúscula per indicar que estava pensada per ser utilitzada amb new.

JavaScript
// Aquesta funció ÉS el constructor.
function Cotxe(marca, any) {
  // ...
}

Les propietats

Les propietats (les dades úniques de cada objecte) es declaraven dins de la funció constructora utilitzant this. Quan cridaves new Cotxe(...), JavaScript creava un objecte buit ({}) i l'assignava a this dins d'aquesta funció. La feina de la funció era "omplir" aquest this.

JavaScript
function Cotxe(marca, any) {
  // Propietats úniques per a cada instància
  this.marca = marca;
  this.any = any;
  this.velocitat = 0;
}

// En crear un objecte, s'executa aquesta funció:
let elMeuCotxe = new Cotxe("Ford", 2014); 
// elMeuCotxe ara és: { marca: "Ford", any: 2014, velocitat: 0 }

Els mètodes (prototype)

Aquí ve la part més important. NO volíem definir els mètodes dins del constructor (com this.arrencar = function() {}), perquè això crearia una còpia de la funció per a cada cotxe creat (un malbaratament de memòria). Per compartir mètodes entre totes les instàncies, els afegíem a l'objecte prototype del constructor.

Què és el prototype? Tota funció de JavaScript té una propietat anomenada prototype, que és un objecte buit. Quan crees una instància amb new, aquesta instància "s'enllaça" secretament amb aquest objecte prototype.

JavaScript
// Afegim un mètode al 'prototype' de Cotxe
Cotxe.prototype.arrencar = function() {
  console.log(`El ${this.marca} s'ha engegat!`);
};

Cotxe.prototype.frenar = function() {
  this.velocitat -= 5;
  console.log(`Velocitat: ${this.velocitat}`);
};

Com funciona?

  1. Creem let elMeuCotxe = new Cotxe("Ford", 2014);.

  2. Cridem elMeuCotxe.arrencar().

  3. JavaScript busca arrencar a elMeuCotxe. No el troba (allà només hi ha marca, any i velocitat).

  4. Com que no el troba, busca al prototype enllaçat (Cotxe.prototype).

  5. Allà sí que el troba! L'executa, i this apunta correctament a elMeuCotxe.

Això s'anomena cadena de prototips (prototype chain) i és el cor de l'herència a JavaScript.

Getters i setters

Abans d'ES6, això era més complicat. No teníem la sintaxi elegant get edat() {}. S'havia de fer servir un mètode estàtic de l'objecte Object anomenat Object.defineProperty().

També s'havien de definir al prototype.

JavaScript
// Volem una propietat "descripcio" que sigui calculada (un GETTER)
Object.defineProperty(Cotxe.prototype, 'descripcio', {
  
  // Aquest 'get' s'executa quan algú fa: elMeuCotxe.descripcio
  get: function() {
    return `${this.marca} de l'any ${this.any}`;
  },
  
  enumerable: true // (perquè aparegui en bucles, etc.)
});


// Volem una propietat "any" que validi (un SETTER)
// (Nota: per això necessitem una variable interna _any)

function Cotxe(marca, any) {
  this.marca = marca;
  this._any = any; // Variable interna
}

Object.defineProperty(Cotxe.prototype, 'any', {
  
  get: function() {
    return this._any;
  },
  
  set: function(nouAny) {
    if (nouAny > 2025) {
      console.error("Aquest any encara no ha arribat!");
    } else {
      this._any = nouAny;
    }
  },
  
  enumerable: true
});

Comparativa

El model de class d'ES6 és només una millora per sobre d'aquest model de prototype que acabem de veure.

Concepte "Classe" ES6 (Modern) Funció Constructora (Antic)
Constructor class Cotxe { constructor() {} } function Cotxe() {}
Propietats constructor() { this.marca = ... } function Cotxe() { this.marca = ... }
Mètodes class Cotxe { arrencar() {} } Cotxe.prototype.arrencar = function() {}
Getters/Setters class Cotxe { get edat() {} } Object.defineProperty(Cotxe.prototype, ...)