Una guida all'ereditarietà delle classi basata su prototipi in JavaScript

I linguaggi informatici spesso forniscono un modo per ereditare un oggetto
un altro oggetto. L'oggetto ereditato contiene tutte le proprietà dal suo oggetto padre. Inoltre, specificherà anche il proprio set di proprietà uniche.

Seguimi su Twitter per suggerimenti su JavaScript e annunci di libri.

Gli oggetti JavaScript utilizzano l'ereditarietà basata sul prototipo. Il suo design è logicamente simile (ma diverso nell'implementazione) dall'eredità di classe in linguaggi di programmazione strettamente orientati agli oggetti.

Può essere liberamente descritto dicendo che quando metodi o proprietà sono collegati al prototipo dell'oggetto diventano disponibili per l'uso su quell'oggetto e i suoi discendenti. Ma questo processo si svolge spesso dietro le quinte.

Durante la scrittura del codice, non sarà nemmeno necessario toccare direttamente la proprietà del prototipo. Quando esegui il metodo split, lo chiameresti direttamente da una stringa letterale come: "ciao" .split ("e") o da una variabile: string.split (",");

Quando usi la classe e estendi le parole chiave internamente, JavaScript utilizzerà comunque l'ereditarietà basata sui prototipi. Semplifica semplicemente la sintassi. Forse è per questo che è importante capire come funziona l'ereditarietà basata sui prototipi. È ancora al centro del design del linguaggio.

Questo è il motivo per cui in molti tutorial vedrai String.prototype.split scritto invece di String.split. Ciò significa che esiste una suddivisione del metodo che può essere utilizzata con oggetti di tipo stringa perché è collegata alla proprietà prototipo di quell'oggetto.

Creazione di una gerarchia logica di tipi di oggetti

Gatto e cane sono ereditati dall'animale che è ereditato dall'animale.

Un cane e un gatto condividono tratti simili. Invece di creare due classi diverse,
possiamo semplicemente creare un animale domestico di classe ed ereditare da esso un cane e un gatto. Ma la stessa classe Pet può anche essere ereditata dalla classe Animal.

Prima di iniziare

Cercare di capire i prototipi è come attraversare il fiume passando dalla programmazione alla progettazione del linguaggio informatico. Due aree di conoscenza completamente diverse.

Tecnicamente, basta una leggera conoscenza della classe ed estende le parole chiave per scrivere software. Cercare di capire il prototipo è come avventurarsi negli angoli più bui del design del linguaggio. E a volte può essere perspicace.

Questo tutorial da solo non sarà sufficiente. Mi sono concentrato solo su alcune cose importanti che speriamo ti guideranno nella giusta direzione.

Sotto il cappuccio

L'idea alla base dell'ereditarietà degli oggetti è quella di fornire una struttura per una gerarchia di
oggetti simili. Puoi anche dire che un oggetto figlio è "derivato" dal suo genitore.

Come vengono create le catene di prototipi in JavaScript.

Tecnicamente, ecco come appare. Cerca di non pensarci troppo. Sappi solo che in cima alla gerarchia c'è l'oggetto Object. Ecco perché il suo prototipo punta a null. Non c'è nient'altro sopra di esso.

Ereditarietà di oggetti basata su prototipi

JavaScript supporta l'ereditarietà degli oggetti tramite qualcosa di noto come prototipo. C'è una proprietà dell'oggetto chiamata prototipo collegata a ciascun oggetto.

Lavorare con la classe ed estendere le parole chiave è facile, ma capire in realtà come funziona l'ereditarietà basata sui prototipi non è banale. Speriamo che questo tutorial sollevi almeno un po 'di nebbia!

Funzioni del costruttore di oggetti

Le funzioni possono essere utilizzate come costruttori di oggetti. Il nome di una funzione di costruzione di solito inizia con una lettera maiuscola per tracciare la distinzione tra funzioni regolari. I costruttori di oggetti vengono utilizzati per creare un'istanza di un oggetto.

Alcuni degli oggetti incorporati JavaScript erano già stati creati seguendo le stesse regole. Ad esempio, Number, Array e String sono ereditati da Object. Come discusso in precedenza, ciò significa che qualsiasi proprietà collegata a Object diventa automaticamente disponibile su tutti i suoi figli.

Costruttori

È impossibile comprendere il prototipo senza comprendere l'anatomia delle funzioni del costruttore.

Quindi, cosa succede esattamente quando creiamo una funzione di costruzione personalizzata? Due proprietà appaiono magicamente nella nostra definizione di classe: constructor e prototype.constructor.

Non indicano lo stesso oggetto. Analizziamoli:

Diciamo che definiamo una nuova classe Crane (usando la funzione o la parola chiave class.)

Un costruttore personalizzato che abbiamo appena creato è ora collegato alla proprietà prototipo della nostra classe Crane personalizzata. È un collegamento che punta al proprio costruttore. Crea una logica circolare. Ma questo è solo un pezzo del puzzle.

Diamo ora un'occhiata a Crane.constructor:

Crane.constructor stesso indica il tipo di oggetto da cui è stato creato.
Poiché tutti i costruttori di oggetti sono nativamente funzioni, l'oggetto indicato da Crane.constructor è un oggetto di tipo Funzione, in altre parole il costruttore di funzioni.

Questa dinamica tra Crane.prototype.constructor e Crane.constructor è ciò che consente l'ereditarietà dei prototipi a livello molecolare. Raramente devi anche pensarci quando scrivi il codice JavaScript. Ma questa è sicuramente una domanda per l'intervista.

Vediamo brevemente di nuovo questo. Crane.prototype.constructor punta al proprio costruttore. È quasi come dire "Sono io".

La stessa cosa esatta accade quando si definisce una classe usando la parola chiave class:

Ma la proprietà Crane.constructor punta al costruttore di funzioni.

Ed è così che viene stabilito il collegamento.

Ora l'oggetto Crane stesso può essere il "prototipo" di un altro oggetto. E quell'oggetto può essere il prototipo di un altro oggetto. E così via. La catena può continuare all'infinito.

Nota a margine: nel caso delle funzioni di tipo ES5, la funzione stessa è la
costruttore. Ma la parola chiave della classe ES6 colloca il costruttore all'interno del suo ambito. Questa è solo una differenza sintattica.

Ereditarietà basata sul prototipo

Dovremmo sempre usare la classe ed estendere le parole chiave per creare ed ereditare oggetti. Ma sono solo un involucro di caramelle per ciò che accade realmente dietro le quinte.

Anche se la creazione di gerarchie di ereditarietà degli oggetti utilizzando la sintassi in stile ES5 è obsoleta e raramente vista tra gli sviluppatori di software professionali, capendo che otterrai una visione più approfondita di come funziona effettivamente.

Definiamo un nuovo oggetto Bird e aggiungiamo 3 proprietà: tipo, colore e uova. Aggiungiamo anche 3 metodi: volare, camminare e lay_egg. Qualcosa che tutti gli uccelli possono fare:

Nota che ho intenzionalmente disattivato il metodo lay_egg. Ricorda come noi
discusso in precedenza che Bird.prototype punta al proprio costruttore?

In alternativa, avresti potuto collegare il metodo di deposizione delle uova direttamente a Bird.prototype come mostrato nell'esempio seguente:

A prima vista può sembrare che non vi sia alcuna differenza tra i metodi di collegamento utilizzando questa parola chiave all'interno di Bird e semplicemente aggiungendola direttamente alla proprietà Bird.prototype. Perché funziona ancora bene?

Ma questo non è del tutto vero. Per ora non entrerò nei dettagli, perché qui non capisco perfettamente la distinzione. Ma ho in programma di aggiornare questo tutorial quando raccoglierò ulteriori informazioni sull'argomento.

(i commenti dei prototipi di veterani sono i benvenuti!)

Non tutti gli uccelli sono fatti allo stesso modo

L'intero punto dell'ereditarietà degli oggetti è usare una classe comune che definisce tutte le proprietà e tutti i metodi che erediteranno automaticamente tutti i figli di quella classe. Questo rende il codice più breve e consente di risparmiare memoria.

(Immagina di definire le stesse proprietà e gli stessi metodi su tutti gli oggetti secondari individualmente da capo. Ci vorrebbe il doppio della memoria.)

Creiamo diversi tipi di uccelli. Anche se tutti possono ancora volare, camminare e lay_eggs (perché sono ereditati dalla classe Bird principale), ogni tipo di uccello unico aggiungerà i suoi metodi unici a quella classe. Ad esempio, solo i pappagalli possono parlare. E solo i corvi possono risolvere enigmi. Solo un uccello canoro può cantare.

Pappagallo
Creiamo un pappagallo ed ereditiamolo da Bird:

Parrot è una normale funzione di costruzione proprio come Bird.

La differenza è che chiamiamo il costruttore di Bird con Bird.call e passiamo questo contesto al Parrot, prima di collegare i nostri metodi. Bird.call aggiunge semplicemente tutte le sue proprietà e metodi a Parrot. Oltre a ciò, stiamo anche aggiungendo il nostro metodo: parlare.

Ora i pappagalli possono volare, camminare, deporre le uova e parlare! Ma non abbiamo mai dovuto definire i metodi fly walk e lay_eggs all'interno di Parrot stesso.

Corvo
Allo stesso modo, creiamo Raven ed ereditiamolo da Bird:

I corvi sono unici in quanto possono risolvere enigmi.

uccello canoro
Ora creiamo Songbird ed ereditiamolo da Bird:

Gli uccelli canori possono cantare.

Testing The Birds

Abbiamo appena creato un gruppo di uccelli diversi con abilità uniche. Vediamo cosa
sono capaci di! Fino ad ora abbiamo definito solo le classi e stabilito le loro
relazione gerarchica.

Per lavorare con gli oggetti dobbiamo istanziarli:

Istanziamo un passero usando il costruttore Bird originale:

Sparrow può volare, camminare e deporre le uova, perché è stato ereditato da Bird che definisce tutti questi metodi.

Ma un passero non può parlare. Perché non è un pappagallo.

Creiamo un parrocchetto dalla classe Parrot:

Poiché Parrot è ereditato da Bird, otteniamo tutti i suoi metodi. Un parrocchetto ha la capacità unica di parlare, ma non può cantare! Il metodo sing è disponibile solo su oggetti di tipo Songbird. Ereditiamo lo storno dalla classe Songbird:

Infine, creiamo un corvo e risolviamo alcuni enigmi:

Utilizzando la classe e estende le parole chiave

I costruttori in stile ES5 possono essere un po 'ingombranti.

Fortunatamente ora abbiamo classe ed estende le parole chiave per realizzare esattamente la stessa cosa che abbiamo appena fatto nella sezione precedente.

la classe sostituisce la funzione

extends e super () sostituiscono Bird.call dagli esempi precedenti.

Nota che dobbiamo usare super () che chiama il costruttore della classe genitore.

Questa sintassi sembra molto più gestibile!

Ora tutto ciò che dobbiamo fare è creare un'istanza dell'oggetto:

Panoramica

L'ereditarietà delle classi aiuta a stabilire una gerarchia di oggetti.

Le classi sono i mattoni fondamentali del design e dell'architettura dell'applicazione. Rendono il lavoro con il codice un po 'più umano.

Certo, Bird era solo un esempio. In uno scenario del mondo reale, potrebbe essere qualsiasi cosa in base al tipo di applicazione che stai cercando di creare.

La classe del veicolo può essere un genitore di Moto, Auto o Carro armato.

I pesci possono essere usati per ereditare lo squalo, il pesce rosso, il luccio e così via.

L'ereditarietà ci aiuta a scrivere codice più pulito e riutilizzare l'oggetto principale per risparmiare memoria sulla ripetizione delle proprietà degli oggetti e delle definizioni dei metodi.