Skip to content →

Junior Javascript Developer: Prototipi

Iniziamo questa serie dedicata a chi comincia (o ha cominciato da poco) ad approcciarsi con il mondo javascript.

Non puoi andare molto lontano in JavaScript senza conoscere il concetto di “oggetti“.

Sono fondamentali in quasi ogni aspetto di JavaScript. In effetti, imparare a creare oggetti è, probabilmente, una delle prime cose che hai iniziato a studiare. Detto questo, per apprendere in modo più efficace i prototipi in JavaScript, partiremo proprio da questo.

Fondamentalmente gli oggetti sono coppie chiave/valore. Il modo più comune per creare un oggetto è utilizzare le parentesi graffe {} e aggiungere proprietà e metodi ad un oggetto usando la notazione a punti.

let person = {}
person.name = 'Valerio'
person.age = 34
person.star = 5

person.learn = function (amount) {
  console.log(`${this.name} is learning.`)
  this.star += amount
}

person.laze = function (amount) {
  console.log(`${this.name} is lazing.`)
  this.star -= amount
}

person.read = function (amount) {
  console.log(`${this.name} is reading.`)
  this.star += amount
}

Il passo successivo è incapsulare questa logica all’interno di una funzione che possiamo invocare ogni volta che dobbiamo creare un nuovo oggetto person.

Chiameremo questo pattern Functional Instantiation e chiameremo la funzione stessa “funzione di costruzione” poiché è responsabile della “costruzione” di un nuovo oggetto.

function Person (name, star) {
  let person = {}
  animal.name = name
  animal.star = star

  person.learn = function (amount) {
    console.log(`${this.name} is learning.`)
    this.star += amount
  }

  person.laze = function (amount) {
    console.log(`${this.name} is lazing.`)
    this.star += amount
  }

  person.read = function (amount) {
    console.log(`${this.name} is reading.`)
    this.star -= amount
  }

  return person
}

const valerioObj = Person('Valerio', 7)
const leoObj = Person('Leo', 10)

Ora ogni volta che vogliamo creare un nuovo oggetto person (o più in generale una nuova “istanza”), tutto ciò che dobbiamo fare è invocare la nostra funzione Person.

Ma quali sono gli svantaggi (se ci sono) di questo approccio?

Ognuno di questi metodi non è solo dinamico, ma è anche completamente generico. Ciò significa che non c’è motivo di ricreare questi metodi ogni volta che instanziamo un nuovo oggetto person. Stiamo solo sprecando memoria e rendendo ogni oggetto person più grande di quello che deve essere. La soluzione? Creare un oggetto shared; fondamentalmente questo pattern può essere chiamato Functional Instantiation with Shared Methods.

personMethods: {
   learn(amount) {
    console.log(`${this.name} is learning.`)
    this.star += amount
  }

  laze(amount) {
    console.log(`${this.name} is lazing.`)
    this.star += amount
  }

  read (amount) {
    console.log(`${this.name} is reading.`)
    this.star -= amount
  }
}

function Person (name, star) {
  let person = {}
  person.name = name
  person.star = star
  person.learn = personMethods.learn
  person.laze = personMethods.laze
  person.read = personMethods.read
}

const valerioObj = Person('Valerio', 7)
const leoObj = Person('Leo', 10)

Spostando i metodi condivisi sul proprio oggetto e facendo riferimento a quell’oggetto all’interno della nostra funzione Person, abbiamo risolto il problema dello spreco di memoria.

Se volessimo migliorare il nostro codice, potremmo farlo attraverso Object.create.

Object.create permette di creare un oggetto che delegherà ad un altro oggetto le ricerche fallite.

In sostanza, se creiamo un oggetto con Object.create, nel caso in cui c’è una proprietà che tale oggetto non possiede, quest’ultimo potrà consultare un altro oggetto e vedere se gliela può fornire. In pratica:

const parent = {
  name: 'Valerio',
  age: 35,
  heritage: 'Italian'
}

const child = Object.create(parent)
child.name = 'Leo'
child.age = 1

console.log(child.name) // Leo
console.log(child.age) // 1
console.log(child.heritage) // Italian

Come possiamo notare dall’esempio, child delegherà all’oggetto parent di prelevare la property heritage.

Ora che abbiamo chiarito il concetto di Object.create, applichiamolo al nostro esempio.

personMethods: {
   learn(amount) {
    console.log(`${this.name} is learning.`)
    this.star += amount
  }

  laze(amount) {
    console.log(`${this.name} is lazing.`)
    this.star += amount
  }

  read (amount) {
    console.log(`${this.name} is reading.`)
    this.star -= amount
  }
}

function Person (name, star) {
  let person = Object.create(personMethods)
  person.name = name
  person.star = star
}

const valerioObj = Person('Valerio', 7)
const leoObj = Person('Leo', 10)

leoObj.learn(10)

Quindi ora quando chiamiamo leoObj.learn, JavaScript cercherà il metodo learn sull’oggetto leoObj. Tale ricerca fallirà, e siccome abbiamo usato Object.create, la ricerca verrà delegata all’oggetto personMethods che è dove troverà la callback learn.

Funziona ma … possiamo fare ancora meglio. Sembra solo un pò confusionario dover gestire un oggetto separato (personMethods) per condividere i metodi tra le istanze.

Ed ecco che entra in scena il prototype.

Cosa è esattamente un prototype in JS? Semplicemente, ogni funzione JS ha un proprietà prototype che referenzia un oggetto.

E se invece di creare un oggetto separato per gestire i nostri metodi (come stiamo facendo con personMethods), mettessimo ciascuno di questi metodi sul prototipo della funzione Person?

Quindi tutto ciò che dovremmo fare è usare Object.create per delegare Person.prototype invece che personMethods. Chiameremo questo modello Prototypal Instantiation (in inglese suona meglio :)).

function Person (name, star) {
  let person = Object.create(Person.prototype)
  person.name = name
  person.star = star
  
  return person
}


Person.prototype.learn = (amount) => {
    console.log(`${this.name} is learning.`)
    this.star += amount
}

Person.prototype.laze = (amount) => {
    console.log(`${this.name} is lazing.`)
    this.star += amount
}

Person.prototype.read = (amount) => {
    console.log(`${this.name} is reading.`)
    this.star -= amount
}

const valerioObj = Person('Valerio', 7)
const leoObj = Person('Leo', 10)

leoObj.learn(10)

Fantastico vero? 🙂

Ancora una volta voglio sottolineare che il prototype è semplicemente una proprietà che ogni funzione in JS possiede, e, come nell’esempio sopra, permette di condividere metodi tra tutte le istanze di una funzione.

A questo punto sappiamo 3 cose:

  • Come creare una funzione di constructor.
  • Come aggiungere metodi al prototype della funzione di constructor.
  • Come utilizzare Object.create per delegare le ricerche non riuscite al prototype della funzione.

Questi tre punti sono fondamentali per qualsiasi linguaggio di programmazione.

Nei prossimi post, analizzeremo nel dettaglio tutto ciò che gira intorno ai prototype.

Spero che questo articolo sia stato utile. Se si, condividilo con i tuoi amici, colleghi o con chi ritieni possa essere interessato.

Published in Javascript world Series

Comments

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *