W3docs

Ereditarietà Prototipale in JavaScript

Impara l'ereditarietà prototipale in JavaScript: il link nascosto [[Prototype]], __proto__ vs Object.getPrototypeOf/setPrototypeOf, la catena dei prototipi, F.prototype, il shadowing delle proprietà e come si lega this lungo la catena.

In JavaScript, gli oggetti possono ereditare proprietà e metodi da altri oggetti attraverso un meccanismo chiamato ereditarietà prototipale. Invece di copiare funzionalità da una classe (come in Java o C++), ogni object mantiene un link nascosto verso un altro object — il suo prototipo — e JavaScript segue quel link ogni volta che non trova una proprietà sull'object stesso. Questa pagina illustra come funziona quel link nascosto, la differenza tra __proto__ e Object.getPrototypeOf/setPrototypeOf, come viene esplorata la catena dei prototipi, il ruolo di F.prototype per le funzioni costruttore, il shadowing delle proprietà e come si comporta this lungo la catena.

Ogni object JavaScript ha una proprietà interna nascosta chiamata [[Prototype]]. È o null o un riferimento a un altro object, e quell'object di riferimento è detto prototipo dell'object.

[[Prototype]] è uno slot interno della specifica del linguaggio — non è possibile leggerlo con un normale accesso alle proprietà. Lo si manipola invece attraverso due API pubbliche:

  • L'accessor storico __proto__ (un getter/setter esposto da Object.prototype).
  • I metodi moderni e consigliati Object.getPrototypeOf(obj) e Object.setPrototypeOf(obj, proto).

Il modo più semplice per impostare un prototipo è usare __proto__ all'interno di un object literal. Qui rendiamo animal il prototipo di rabbit, cosicché rabbit possa leggere le proprietà di animal:


javascript— editable

__proto__ vs Object.getPrototypeOf / setPrototypeOf

__proto__ è un getter/setter che da tempo è deprecato per uso generale — è standardizzato solo per compatibilità con i browser. Nel codice reale, si preferiscono i metodi espliciti:


javascript— editable

Nota la differenza: __proto__ è un accessor di proprietà, mentre getPrototypeOf/setPrototypeOf sono funzioni. Due regole pratiche:

  • __proto__ non è la stessa cosa di [[Prototype]]. __proto__ è semplicemente l'accessor che legge/scrive lo slot interno [[Prototype]].
  • Evita di modificare un prototipo dopo che un object è stato creato. Object.setPrototypeOf e l'assegnazione di obj.__proto__ sono operazioni lente: i motori JavaScript ottimizzano molto gli oggetti il cui prototipo è fisso al momento della creazione. Imposta il prototipo una volta sola, quando costruisci l'object.

La catena dei prototipi

Anche il prototipo di un prototipo può avere un proprio prototipo, formando una catena dei prototipi. Quando si legge obj.prop, JavaScript:

  1. Cerca prop come proprietà propria di obj.
  2. Se non la trova, segue [[Prototype]] e la cerca sul prototipo.
  3. Ripete il passo 2 risalendo la catena finché non trova prop o raggiunge null.

Se la catena termina in null senza trovare la proprietà, il risultato è undefined.


javascript— editable

Ci sono due limiti sulla catena: i riferimenti non devono formare un ciclo (JavaScript lancia un errore se si tenta di creare un ciclo), e [[Prototype]] deve essere un object oppure null.

Shadowing delle proprietà: scrittura vs lettura

Il prototipo viene consultato solo per la lettura. Quando si scrive o si elimina una proprietà, l'operazione agisce sempre sull'object stesso, mai sul suo prototipo. Assegnare una proprietà che esiste anche sul prototipo crea una proprietà propria che nasconde (fa shadowing) quella ereditata:


javascript— editable

L'eccezione riguarda le proprietà accessor (getter/setter): poiché un setter è una chiamata di funzione, scrivere tramite un setter ereditato esegue quel setter invece di creare una nuova proprietà dati propria.

this è sempre l'object chiamante

Una fonte comune di confusione: indipendentemente da dove un metodo si trova nella catena, this al suo interno è l'object prima del punto quando il metodo è stato chiamato — mai il prototipo su cui è stato definito. I metodi ereditati operano quindi sullo stato proprio dell'object che eredita:


javascript— editable

Questo è ciò che rende utili i metodi condivisi su un prototipo: una sola definizione del metodo, ma ogni object memorizza i propri dati.

Funzioni costruttore e F.prototype

Impostare [[Prototype]] manualmente per ogni object è laborioso. Il pattern classico è una funzione costruttore usata con new. Ogni funzione ha una proprietà ordinaria chiamata prototype (scritta F.prototype). Quando si chiama new F(), il [[Prototype]] dell'object appena creato viene impostato su F.prototype.


javascript— editable

Tieni distinti F.prototype dal [[Prototype]] di un object: F.prototype è una proprietà ordinaria della funzione costruttore che fornisce il [[Prototype]] agli oggetti creati con new F(). Per impostazione predefinita F.prototype è un object con una singola proprietà non enumerabile constructor che punta di nuovo alla funzione stessa.


javascript— editable

Nota: La sintassi moderna class di ES6 è zucchero sintattico esattamente su questo meccanismo. Una dichiarazione class crea una funzione costruttore, inserisce i suoi metodi su Constructor.prototype e collega la catena con gli stessi link [[Prototype]]. Vedi Ereditarietà delle classi JavaScript per la forma con extends/super.

Creare oggetti con Object.create

Object.create(proto) costruisce un nuovo object con [[Prototype]] impostato direttamente su proto — senza bisogno di un costruttore. È il modo più esplicito per impostare l'ereditarietà, e accetta un secondo argomento: una mappa di descrittori di proprietà, nello stesso formato usato da Object.defineProperties.


javascript— editable

La mappa dei descrittori consente di controllare i flag come writable, enumerable e configurable — vedi Flag e Descrittori di Proprietà JavaScript per il significato di ciascun flag. Gli oggetti creati con Object.create(null) (detti oggetti "molto semplici") sono trattati in Metodi del Prototipo, Oggetti senza __proto__.

Ereditarietà a più livelli

Poiché ogni prototipo può avere il proprio prototipo, è possibile costruire catene di più livelli per modellare relazioni più specifiche:


javascript— editable

Ispezionare e iterare la catena

Per verificare se un object si trova da qualche parte nella catena di un altro object, si usa isPrototypeOf. Per separare le proprietà proprie da quelle ereditate si usa hasOwnProperty — nota che for...in percorre l'intera catena (solo proprietà enumerabili), mentre Object.keys restituisce solo le chiavi proprie.


javascript— editable

Anche i tipi built-in come array, funzioni e date si affidano a questa catena — i loro metodi vivono su Array.prototype, Function.prototype e così via. Vedi Prototipi Nativi JavaScript per come i built-in sono collegati tra loro.

  • Ogni object ha un [[Prototype]] nascosto che è un altro object oppure null.
  • Si legge [[Prototype]] con Object.getPrototypeOf e lo si imposta con Object.setPrototypeOf; __proto__ è l'accessor legacy e modificare un prototipo dopo la creazione è lento.
  • Le letture risalgono la catena dei prototipi finché la proprietà viene trovata o la catena termina con null; le scritture e le eliminazioni agiscono sempre sull'object stesso e possono fare shadowing delle proprietà ereditate.
  • this all'interno di un metodo è l'object su cui il metodo è stato chiamato, non il prototipo dove è stato definito.
  • new F() imposta il [[Prototype]] del nuovo object su F.prototype; la class di ES6 è zucchero sintattico su questo stesso meccanismo — vedi Ereditarietà delle classi JavaScript.

Pratica

Pratica
Quali affermazioni descrivono accuratamente l'ereditarietà prototipale in JavaScript?
Quali affermazioni descrivono accuratamente l'ereditarietà prototipale in JavaScript?
Was this page helpful?