20 marzo 2025

Padroneggiare gli Oggetti e i Prototipi in JavaScript

JavaScript è fondamentalmente progettato attorno agli oggetti. Gli oggetti sono un aspetto centrale del linguaggio e comprenderli a fondo è essenziale per padroneggiare JavaScript. In questo articolo esploreremo concetti chiave come la creazione degli oggetti, i prototipi, l’ereditarietà e come la parola chiave this opera nei diversi contesti.

Creazione degli Oggetti

Gli oggetti in JavaScript possono essere creati in vari modi, e ogni approccio ha i suoi vantaggi unici. Esploriamo più da vicino i diversi metodi per creare oggetti.

Utilizzando gli Inizializzatori di Oggetti

Gli inizializzatori di oggetti, noti anche come letterali di oggetti, offrono un modo pulito e conciso per definire gli oggetti. La sintassi per creare oggetti usando i letterali di oggetti è semplice:

const obj = {
  property1: value1, // property name may be an identifier
  2: value2, // or a number
  "property n": value3, // or a string
};

In questa sintassi:

const key = "name";
const obj = {
  [key]: "John", // dynamic property name
};
console.log(obj.name); // "John"

I letterali di oggetti sono espressioni, e ogni volta che vengono eseguiti, viene creato un nuovo oggetto. Gli inizializzatori di oggetti identici producono oggetti distinti che non vengono considerati uguali, anche se i loro contenuti sono gli stessi.

Utilizzando una Funzione Costruttore

Un altro modo per creare oggetti è tramite l’uso di funzioni costruttore. Ecco come funziona:

  1. Definisci un tipo di oggetto scrivendo una funzione costruttore. Di solito, inizia con una lettera maiuscola per seguire una convenzione.
  2. Crea un’istanza dell’oggetto utilizzando la parola chiave new.
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person1 = new Person("John", 30);
console.log(person1.name); // "John"

Le funzioni costruttore sono potenti, poiché ti permettono di definire oggetti con proprietà e metodi specifici.

Utilizzando il Metodo Object.create()

Il metodo Object.create() offre un modo per creare un oggetto e specificare il suo prototipo. Questo metodo consente maggiore flessibilità rispetto alle funzioni costruttore, poiché non richiede la definizione di una funzione costruttore.

const prototype = {
  greet() {
    console.log("Hello!");
  },
};

const obj = Object.create(prototype);
obj.greet(); // "Hello!"

Object.create() ti consente di impostare il prototipo dell’oggetto appena creato, offrendo un modo semplice per stabilire l’ereditarietà.

Ereditarietà Prototipale

L’ereditarietà è un concetto fondamentale nella programmazione orientata agli oggetti. In JavaScript, l’ereditarietà viene implementata utilizzando i prototipi. Ogni oggetto JavaScript ha un oggetto prototipo, e questo oggetto prototipo ha a sua volta un prototipo, creando così una catena di prototipi.

Ereditarietà con la Catena dei Prototipi

Quando cerchi di accedere a una proprietà di un oggetto, JavaScript verifica innanzitutto se la proprietà esiste sull’oggetto stesso. Se non esiste, JavaScript cerca la proprietà nel prototipo dell’oggetto, e la ricerca continua lungo la catena dei prototipi fino a quando la proprietà non viene trovata o fino a quando non si raggiunge la fine della catena.

Considera il seguente esempio:

const obj = {
  a: 1,
  b: 2,
  __proto__: {
    b: 3,
    c: 4,
    __proto__: {
      d: 5,
    },
  },
};

console.log(obj.d); // 5

In questo esempio, la proprietà d viene trovata attraversando la catena dei prototipi, partendo da obj e salendo attraverso i suoi prototipi.

La Parola Chiave this

In JavaScript, la parola chiave this si riferisce al contesto in cui una funzione viene chiamata. È fondamentale comprendere come this si comporta in diversi scenari.

Il Contesto di this

Il valore di this dipende da come una funzione viene invocata:

Le Funzioni Arrow e this

Le funzioni arrow si comportano in modo diverso per quanto riguarda la parola chiave this. Invece di avere un proprio this, le funzioni arrow ereditano this dal contesto che le racchiude, rendendole utili per preservare il contesto nelle callback e nei gestori di eventi.

const globalObject = this;
const foo = () => this;

console.log(foo() === globalObject); // true

Poiché le funzioni arrow ereditano this, non è possibile impostare esplicitamente il loro this utilizzando metodi come bind(), call() o apply().

Classi (ES6)

In ES6, JavaScript ha introdotto le classi come una sintassi semplificata per l’ereditarietà basata su prototipi esistente. Le classi offrono un modo più chiaro e strutturato per creare oggetti e gestire l’ereditarietà.

Definizione della Classe

Le classi possono essere definite in due modi:

  1. Dichiarazione della Classe:
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
  1. Espressione della Classe:
const Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Puoi anche definire una classe anonima o fornire un nome per l’espressione della classe, come mostrato sopra.

Corpo della Classe

Il corpo di una classe è racchiuso tra parentesi graffe {}. Qui è dove definisci i membri della classe, come le proprietà e i metodi.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  area() {
    return this.height * this.width;
  }
}

Le classi in JavaScript seguono le regole della modalità strict per default, il che significa che alcuni comportamenti (come l’uso di variabili non dichiarate) sono vietati.

Metodi e Campi Statici

I metodi e i campi statici sono definiti sulla classe stessa, non sulle singole istanze. I metodi statici sono spesso utilizzati per funzioni di utilità, mentre i campi statici possono essere utilizzati per il caching o per dati condivisi tra le istanze.

class Rectangle {
  static description = "I am a rectangle class";
  static info() {
    return Rectangle.description;
  }
}
console.log(Rectangle.info()); // "I am a rectangle class"

Ereditarietà con le Classi

Le classi JavaScript supportano l’ereditarietà utilizzando la parola chiave extends. Una classe può estendere un’altra classe per ereditare le sue proprietà e i suoi metodi.

Esempio di ereditarietà con le classi:

class Square extends Rectangle {
  constructor(side) {
    super(side, side); // Call the parent class constructor
  }
}

In questo esempio, la classe Square eredita dalla classe Rectangle. La parola chiave super viene utilizzata per chiamare il costruttore della classe padre.