OOP E PROTOTYPE IN JAVASCRIPT

JS

La programmazione orientata agli oggetti è un paradigma che mette al centro gli oggetti anziché le funzioni. Guardiamo l’immagine sotto riportata.

Paradigma OOP

COSA SONO GLI OGGETTI?

Gli oggetti sono strutture dati che integrano al loro interno variabili e funzioni. Le variabili prendono il nome di proprietà e le funzioni si chiamano metodi. I quattro pilastri della OOP sono riportati nell’immagine a fianco.

OOP

INCAPSULAMENTO

Incapsulare vuol dire chiudere in una capsula. Si raggruppano dati e funzioni correlati. Le funzioni o metodi lavorano internamente con i dati per eseguire le operazioni richieste.

Incapsulamento

ASTRAZIONE

Il concetto di astrazione si basa sul “cosa”. l’obbiettivo dell’astrazione è esporre l’essenziale e nascondere le complessità. Prendiamo come esempio un lettore DVD, tale dispositivo elettronico espone una interfaccia utente costituita dai pulsanti start, stop, pausa etc. ma nasconde le complessità hardware che ne stanno dietro.

Astrazione

EREDITARIETA’

Tramite l’ereditarietà un oggetto può accedere a proprietà e metodi di un altro oggetto evitando la duplicazione del codice.

ereditarietà

POLIMORFISMO

Il polimorfismo letteralmente significa avere molte forme. Uno stesso metodo di nome m può avere comportamenti diversi in base al contesto in cui opera.

Polimorfismo

Riassumo brevemente con  l’immagine sotto.

OOP

ACCESSO ALLE PROPRIETA’ DI UN OGGETTO

Come sappiamo per accedere alle proprietà degli oggetti si usa la dot notations, ossia nomeoggetto.nomeproprietà. Guardiamo il codice sotto riportato per chiarire il concetto.

Copy to Clipboard

DA OGGETTO LETTERALE A CONSTRUCTION FUNCTION

A volte per evitare inutili duplicazioni di codice si possono usare quella che vengono chiamate factory function o meglio delle constructor function. Guardare il codice riportato sotto.

Copy to Clipboard

NASCONDERE I DETTAGLI ED ESPORRE SOLO L’ESSENZIALE

Vediamo come in JS si possa semplicemente implementare uno dei quattro pilastri della OOP, l’astrazione. Codice in basso.

Copy to Clipboard

CATENA DEI PROTOTIPI

Il meccanismo in JS dell’ereditarietà è implementato attraverso la catena dei prototipi. Tramite essa un oggetto può accedere a proprietà e metodi di un altro oggetto. Con questo meccanismo evitiamo la duplicazione del codice, ne obj2 ne obj3 hanno bisogno di definire il metodo fn1() a meno che non debbano ridefinirlo. Guardare le due figure in basso.

Prototipi
Prototipi

ESEMPIO DI CODICE

Vediamo un esempio su una catena di prototipi.

Copy to Clipboard

CATENA DEI PROTOTIPI RIPORTATI NEL CODICE

Catena dei prototipi

LA PROPRIETA’ __PROTO__

Utilizzando __proto__ su un oggetto possiamo risalire al suo prototipo. Aggiungendo tutta una serie di istruzioni __proto__ possiamo risalire la catena dei prototipi. È deprecato, per conoscere il prototipo di un oggetto usare getPrototypeOf.

CODICE DI ESEMPIO

Copy to Clipboard

SETPROTOTYPEOF E GETPROTOTYPEOF

 Con Object.setPrototypeOf(object1,object2); si imposta il prototipo di un oggetto. Invece Object.getPrototypeOf(object) restituisce il prototipo dell’oggetto passato in input.
Copy to Clipboard

LA KEYWORD THIS

this è il riferimento all’attuale contesto di esecuzione, ovvero l’oggetto nella quale stiamo operando. La prima cosa che il motore JS fa prima dell’esecuzione della prima riga di codice è creare un contesto globale di esecuzione ovvero un oggetto globale in cui tutto il nostro codice viene eseguito, e fa sì che this sia un riferimento a questo oggetto globale (window). Il valore di this all’interno di una funzione dipende da come viene invocata.

Copy to Clipboard

BINDING ESPLICITO CON BIND, CALL E APPLY

Tramite binding esplicito è possibile fare in modo che this si riferisca a un certo oggetto. Call e Apply lavorano pressochè allo stesso modo, invece Bind prende in input l’oggetto a cui this deve riferirsi e resituisce una riferimento alla funzione.

Copy to Clipboard

THIS E ARROW FUNCTION

Se abbiamo una funzione che deve lavorare con le proprietà di un oggetto usiamo la sintassi classica, altrimenti le arrow function.
Copy to Clipboard

ITERARE UN OGGETTO

Normalmente si utilizza un ciclo for in per scorrere le proprietà di un oggetto, chiavi e valori. Tale ciclo è in grado di risalire la catena dei prototipi. Invece se vogliamo iterare solo l’oggetto escludendo i prototipi si usa il metodo hasOwnProperty.
Copy to Clipboard

OBJECT DEFINEPROPERTY

Se vogliamo aggiungere una nuova proprietà a un oggetto, ne definiamo le caratteristiche, e usiamo il metodo Object.defineProperty(…).

Copy to Clipboard

OBJECT PREVENTEXTENSIONS

Il codice sotto riportato spiega questo metodo JS.

Copy to Clipboard

APPROFONDIMENTO AI

In JavaScript, il prototipo è un meccanismo che permette agli oggetti di ereditare proprietà e metodi da altri oggetti. Ogni oggetto in JavaScript ha un riferimento a un oggetto prototipo da cui eredita caratteristiche. Questo riferimento si trova nella proprietà nascosta [[Prototype]], che è accessibile tramite Object.getPrototypeOf() o la notazione meno comune __proto__.

Costruzione del prototipo

Quando crei un oggetto o una funzione costruttore in JavaScript, quest’ultima ha una proprietà chiamata prototype, che definisce le proprietà e i metodi che tutti gli oggetti creati tramite tale costruttore erediteranno. Per esempio:

function Persona(nome, età) {
       this.nome = nome;
       this.età = età;
}

Persona.prototype.saluta = function() {
       console.log(`Ciao, mi chiamo ${this.nome}`);
};

let persona1 = new Persona(‘Mario‘, 30);
persona1.saluta(); // “Ciao, mi chiamo Mario”

In questo esempio, Persona.prototype è il prototipo di persona1, il che significa che persona1 eredita il metodo saluta().

Catena dei prototipi

Il concetto di “catena dei prototipi” (o Prototype Chain) si riferisce al fatto che un oggetto può ereditare proprietà non solo direttamente dal suo prototipo, ma anche dai prototipi dei suoi prototipi, creando così una catena di ereditarietà.

Quando si accede a una proprietà o un metodo di un oggetto, JavaScript prima controlla se esiste su quell’oggetto. Se non la trova, va a cercare nel suo prototipo, poi nel prototipo del prototipo, e così via, fino a raggiungere l’oggetto Object.prototype, che è il capostipite di tutti gli oggetti in JavaScript.

console.log(persona1.toString()); // Viene chiamato Object.prototype.toString()

In questo esempio, persona1 non ha il metodo toString(), quindi JavaScript cerca nella catena dei prototipi fino a trovare Object.prototype.toString().

Metodi call(), apply() e bind()

call()

Il metodo call() permette di invocare una funzione specificando il valore di this e gli argomenti individualmente. Questo è utile quando si vuole usare una funzione in un contesto differente, associandola a un altro oggetto.

function saluta() {
     console.log(`Ciao, mi chiamo ${this.nome}`);
}

let persona2 = { nome: ‘Anna‘ };
saluta.call(persona2); // “Ciao, mi chiamo Anna”

In questo esempio, il metodo call() imposta il contesto this della funzione saluta su persona2.

apply()

Il metodo apply() è simile a call(), ma invece di passare gli argomenti individualmente, si passa un array di argomenti.

function somma(a, b) {
       console.log(a + b);
}

somma.apply(null, [2, 3]); // 5

Qui, apply() accetta un array di argomenti e li passa alla funzione somma.

bind()

Il metodo bind() crea una nuova funzione con lo stesso corpo e scope della funzione originale, ma con il valore di this fissato a un dato oggetto. È utile quando si vuole legare una funzione a un particolare contesto e usarla successivamente.

function saluta() {
     console.log(`Ciao, mi chiamo ${this.nome}`);
}

let persona3 = { nome: ‘Marco‘ };
let salutaMarco = saluta.bind(persona3);
salutaMarco(); // “Ciao, mi chiamo Marco”

A differenza di call() e apply(), bind() non invoca subito la funzione, ma restituisce una nuova funzione con il contesto this legato.

Differenze tra call(), apply() e bind()

call(): invoca immediatamente la funzione e permette di passare gli argomenti uno per uno.

apply(): invoca immediatamente la funzione ma gli argomenti sono passati in un array.

bind(): non invoca subito la funzione, ma restituisce una nuova funzione con il this fissato.

Conclusione

I concetti di prototipo e catena dei prototipi sono fondamentali per comprendere come funziona l’ereditarietà in JavaScript. I metodi call(), apply() e bind() permettono una gestione flessibile del contesto this, permettendo di riutilizzare funzioni con differenti contesti o vincolarle in modo permanente.

LINK AI POST PRECEDENTI

IL LINGUAGGIO JAVASCRIPT

LINK AL CODICE SU GITHUB

GITHUB