Articolo originale: Object Oriented Programming in JavaScript – Explained with Examples
JavaScript non è un linguaggio orientato agli oggetti o basato su classi. Nonostante ciò ci sono dei modi per utilizzare la programmazione orientata agli oggetti (object oriented programming, OOP).
In questo tutorial, spiegherò la OOP e ti mostrerò come farne uso.
Secondo Wikipedia, la programmazione basata su classi è
uno stile di programmazione orientata agli oggetti (OOP) in cui l'ereditarietà avviene tramite la definizione di classi di oggetti, invece che tramite i soli oggetti.
Il modello più popolare di OOP è quello basato su classi.
Ma come ho menzionato, JavaScript non è un linguaggio basato su classi – è un linguaggio basato sul prototype.
Secondo la documentazione di Mozilla:
Un linguaggio basato sul prototype possiede il concetto di oggetto prototipico, un oggetto usato come stampo dal quale ottenere le proprietà iniziali per un nuovo oggetto.
Dai un'occhiata a questo codice:
let names = {
fname: "Dillion",
lname: "Megida"
}
console.log(names.fname);
console.log(names.hasOwnProperty("mname"));
// Output atteso
// Dillion
// false
La variabile oggetto names
ha solo due proprietà – fname
e lname
. Nessun metodo.
Quindi da dove salta fuori hasOwnProperty
?
Viene fuori dal prototype di Object
.
Prova a vedere sulla console il contenuto della variabile:
console.log(names);
Espandendo il risultato sulla console otterrai questo:

Nota l'ultima proprietà – __proto__
– e prova ad espanderla:

Vedrai una serie di proprietà sotto il costruttore Object
. Tutte queste proprietà derivano dal prototype globale di Object
. Guardando con attenzione noterai anche la nostra proprietà nascosta hasOwnProperty
.
In altre parole, tutti gli oggetti hanno accesso al prototype di Object
. Non possiedono queste proprietà, ma è garantito loro l'accesso alle proprietà nel prototype.
La proprietà __proto__
Questa proprietà punta all'oggetto usato come prototype.
Si tratta di una proprietà di ogni oggetto che gli dà accesso alla proprietà prototype di Object
.
Ogni oggetto ha questa proprietà come impostazione predefinita, che fa riferimento al prototype di Object
, tranne se configurata diversamente (ovvero se __proto__
per quell'oggetto viene puntata su un altro prototype).
Modificare la proprietà __proto__
Questa proprietà può essere modificata definendo esplicitamente che dovrebbe far riferimento a un altro prototype. I seguenti metodi vengono usati a questo scopo:
Object.create()
function DogObject(name, age) {
let dog = Object.create(constructorObject);
dog.name = name;
dog.age = age;
return dog;
}
let constructorObject = {
speak: function(){
return "I am a dog"
}
}
let bingo = DogObject("Bingo", 54);
console.log(bingo);
Ecco come si presenta la console:

Hai notato la proprietà __proto__
e il metodo speak
?
Object.create
fa sì che l'argomento passatole diventi il prototype.
Parola chiave new
function DogObject(name, age) {
this.name = name;
this.age = age;
}
DogObject.prototype.speak = function() {
return "I am a dog";
}
let john = new DogObject("John", 45);
La proprietà __proto__
di john
è diretta verso il prototype di DogObject
. Ma ricorda che il prototype di DogObject
è un oggetto (coppie chiave-valore), dunque possiede anch'esso una proprietà __proto__
che fa riferimento al prototype globale di Object
.
Questa tecnica viene chiamata PROTOTYPE CHAINING (concatenamento di prototype).
Nota che l'approccio con la parola chiave new
fa la stessa cosa di Object.create()
, ma lo rende l'operazione più semplice dato che fa alcune cose automaticamente per te.
Quindi...
Ogni oggetto in JavaScript ha accesso al prototype di Object
di default. Se configurato per fare uso di un altro prototype, diciamo prototype2
, allora prototype2
avrà comunque accesso al prototype di Object
di default e così via.
Combinazione oggetto + funzione
Probabilmente sei confuso dal fatto che DogObject
sia una funzione (function DogObject(){}
) e che abbia delle proprietà accessibili con la notazione a punto. Ciò viene chiamata combinazione funzione oggetto.
Quando le funzioni vengono dichiarate, vengono date loro molte proprietà di default. Ricorda che anche le funzioni sono degli oggetti in JavaScript.
E ora, le classi
La parola chiave class
è stata introdotta in JavaScript con ECMAScript 2015. Rende JavaScript simile a un linguaggio OOP. Ma si tratta solo dello zucchero sintattico sulla base della tecnica di prototyping esistente, la quale continua in background, mentre dall'esterno il tutto sembra avere l'aspetto di una OOP. Adesso cercheremo di capire come sia possibile.
Il seguente esempio è un generico utilizzo di class
in JavaScript:
class Animals {
constructor(name, specie) {
this.name = name;
this.specie = specie;
}
sing() {
return `${this.name} can sing`;
}
dance() {
return `${this.name} can dance`;
}
}
let bingo = new Animals("Bingo", "Hairy");
console.log(bingo);
Questo è il risultato sulla console:

__proto__
fa riferimento al prototype di Animals
(che a sua volta fa riferimento al prototype di Object
).
Da qui, possiamo vedere che il costruttore definisce le caratteristiche principali, mentre tutto ciò che è al di fuori del costruttore (sing()
e dance()
) costituisce delle funzionalità aggiuntive (prototype).
In background, usando l'approccio con la parola chiave new
, quanto sopra si traduce in:
function Animals(name, specie) {
this.name = name;
this.specie = specie;
}
Animals.prototype.sing = function(){
return `${this.name} can sing`;
}
Animals.prototype.dance = function() {
return `${this.name} can dance`;
}
let Bingo = new Animals("Bingo", "Hairy");
Sottoclassi
Questa è una caratteristica della OOP, per cui una classe eredita caratteristiche dalla sua classe genitore ma possiede anche caratteristiche extra che il genitore non ha.
Ad esempio, diciamo di voler creare una classe Cats
. Invece di creare la classe da zero – definendo proprietà come name
, age
e specie
ex novo – possiamo far ereditare delle proprietà dalla classe genitore Animals
.
La classe Cats
può anche avere proprietà extra come wiskerColor
, in questo caso.
Vediamo come ottenere una sottoclasse tramite class
.
Abbiamo bisogno di un genitore da cui la sottoclasse erediterà delle caratteristiche. Esaminiamo il seguente codice:
class Animals {
constructor(name, age) {
this.name = name;
this.age = age;
}
sing() {
return `${this.name} can sing`;
}
dance() {
return `${this.name} can dance`;
}
}
class Cats extends Animals {
constructor(name, age, whiskerColor) {
super(name, age);
this.whiskerColor = whiskerColor;
}
whiskers() {
return `I have ${this.whiskerColor} whiskers`;
}
}
let clara = new Cats("Clara", 33, "indigo");
Con il codice qui sopra, otteniamo il seguente output:
console.log(clara.sing());
console.log(clara.whiskers());
// Output atteso
// "Clara can sing"
// "I have indigo whiskers"
Osservando il contenuto di clara
nella console, vedremo:

Puoi notare che clara
ha una proprietà __proto__
che fa riferimento al costruttore Cats
e ha accesso al metodo whiskers()
. La proprietà __proto__
ha a sua volta una proprietà __proto__
che fa riferimento al costruttore Animals
avendo così accesso a sing()
e dance()
. name
e age
sono proprietà che esistono in ogni oggetto creato così.
Usando l'approccio del metodo Object.create
, quanto sopra diventa:
function Animals(name, age) {
let newAnimal = Object.create(animalConstructor);
newAnimal.name = name;
newAnimal.age = age;
return newAnimal;
}
let animalConstructor = {
sing: function() {
return `${this.name} can sing`;
},
dance: function() {
return `${this.name} can dance`;
}
}
function Cats(name, age, whiskerColor) {
let newCat = Animals(name, age);
Object.setPrototypeOf(newCat, catConstructor);
newCat.whiskerColor = whiskerColor;
return newCat;
}
let catConstructor = {
whiskers() {
return `I have ${this.whiskerColor} whiskers`;
}
}
Object.setPrototypeOf(catConstructor, animalConstructor);
const clara = Cats("Clara", 33, "purple");
clara.sing();
clara.whiskers();
// Output atteso
// "Clara can sing"
// "I have purple whiskers"
Object.setPrototypeOf
è un metodo che accetta due argomenti – l'oggetto (primo argomento) e il prototype desiderato (secondo argomento).
Nel codice qui sopra, la funzione Animals
restituisce un oggetto con animalConstructor
come prototype. La funzione Cats
restituisce un oggetto con catConstructor
come prototype. catConstructor
, d'altro canto, ha il prototype di animalConstructor
.
Dunque, animali comuni hanno accesso solo a animalConstructor
ma i gatti (cat) hanno accesso a catConstructor
e animalConstructor
.
In conclusione
JavaScript sfrutta la sua natura basata sul prototype per accogliere gli sviluppatori OOP nel suo ecosistema. Fornisce anche dei modi semplici per creare prototype e organizzare dati correlati.
I veri linguaggi OOP non eseguono prototyping in background – ricordalo.
Un sentito ringraziamento a Will Sentance. Tutto ciò che vedi in questo articolo (eccetto qualche cosuccia extra) l'ho imparato grazie al suo corso "JavaScript: The Hard Parts of Object Oriented JavaScript". Dovresti dargli un'occhiata.
Contattami su Twitter per domande o contributi.
Grazie per aver letto : )