Invocare Metodi in JavaScript

Concetti Chiave
  • Un metodo non è altro che una funzione JavaScript memorizzata in una proprietà di un oggetto.
  • I metodi vengono invocati su un oggetto e il contesto di invocazione determina il valore di this all'interno del metodo.
  • La sintassi di invocazione di metodo è un'espressione di accesso a proprietà seguita da una lista di argomenti tra parentesi.
  • Le invocazioni di metodi possono essere concatenate, permettendo di invocare più metodi su un oggetto in una singola espressione.
  • La parola chiave this all'interno di un metodo si riferisce all'oggetto su cui il metodo è stato invocato, e non eredita il valore this della funzione contenitrice.

Invocazione di Metodi

Un metodo non è altro che una funzione JavaScript memorizzata in una proprietà di un oggetto. Se abbiamo una funzione f e un oggetto o, possiamo definire un metodo chiamato m di o con la seguente riga:

let o = {};

let f = function(x, y) {
    // Corpo della funzione
};

// Definisce un metodo m di o
// che chiama la funzione f
o.m = f;

Avendo definito il metodo m() dell'oggetto o, lo invochiamo così:

o.m();

Oppure, se m() si aspetta due argomenti, potremmo invocarlo così:

o.m(x, y);

Il codice in questo esempio è un'espressione di invocazione: include un'espressione di funzione o.m e due espressioni di argomenti, x e y. L'espressione di funzione è essa stessa un'espressione di accesso a proprietà, e questo significa che la funzione viene invocata come metodo piuttosto che come funzione normale.

Gli argomenti e il valore di ritorno di un'invocazione di metodo sono gestiti esattamente come descritto per l'invocazione di funzioni normali. Le invocazioni di metodi differiscono dalle invocazioni di funzioni in un modo importante, tuttavia: il contesto di invocazione. Le espressioni di accesso a proprietà consistono di due parti: un oggetto (in questo caso o) e un nome di proprietà (m). In un'espressione di invocazione di metodo come questa, l'oggetto o diventa il contesto di invocazione, e il corpo della funzione può riferirsi a quell'oggetto usando la parola chiave this. Ecco un esempio concreto:

let calcolatrice = { // Un oggetto letterale
    operando1: 1,
    operando2: 1,

    // Stiamo usando la sintassi abbreviata per metodi per questa funzione
    aggiungi() {
        // Si noti l'uso della parola chiave this per riferirsi all'oggetto contenitore.
        this.risultato = this.operando1 + this.operando2;
    }
};

calcolatrice.aggiungi();  // Un'invocazione di metodo per calcolare 1+1.
calcolatrice.risultato  // => 2

La maggior parte delle invocazioni di metodi usa la notazione punto per l'accesso alle proprietà, ma le espressioni di accesso a proprietà che usano parentesi quadre causano anche l'invocazione di metodi. Le seguenti sono entrambe invocazioni di metodi, per esempio:

// Un altro modo per scrivere o.m(x,y).
o["m"](x,y);

// Anche questa è un'invocazione di metodo (assumendo che a[0] sia una funzione).
a[0](z);

Le invocazioni di metodi possono anche coinvolgere espressioni di accesso a proprietà più complesse:

// Invoca il metodo su cliente.cognome
cliente.cognome.toUpperCase();

// Invoca il metodo m() sul valore di ritorno di f()
f().m();

I metodi e la parola chiave this sono centrali al paradigma di programmazione orientata agli oggetti. Qualsiasi funzione che viene usata come metodo viene effettivamente passata un argomento implicito, ossia l'oggetto attraverso il quale viene invocata. Tipicamente, un metodo esegue qualche tipo di operazione su quell'oggetto, e la sintassi di invocazione di metodo è un modo elegante per esprimere il fatto che una funzione sta operando su un oggetto. Confrontiamo le seguenti due righe:

rettangolo.impostaDimensione(larghezza, altezza);
impostaRettangoloDimensione(rettangolo, larghezza, altezza);

Le funzioni ipotetiche invocate in queste due righe di codice potrebbero eseguire esattamente la stessa operazione sull'oggetto (ipotetico) rettangolo, ma la sintassi di invocazione di metodo nella prima riga indica più chiaramente l'idea che è l'oggetto rettangolo che è il focus principale dell'operazione.

Concatenazione di Metodi

Quando i metodi restituiscono oggetti, si può utilizzare il valore di ritorno di un'invocazione di metodo come parte di un'invocazione successiva. Questo risulta in una serie (o catena) di invocazioni di metodi come singola espressione. Quando si lavora con operazioni asincrone basate su Promise (che vedremo nelle prossime lezioni), per esempio, è comune scrivere codice strutturato così:

// Esegue tre operazioni asincrone in sequenza
// gestendo gli errori.
faiPassoUno().then(faiPassoDue).then(faiPassoTre).catch(gestisciErrori);

Quando si scrive un metodo che non ha un valore di ritorno proprio, spesso si sceglie di far restituire al metodo this. Questo stile di programmazione è conosciuto come concatenazione di metodi in cui un oggetto può essere nominato una volta e poi possono essere invocati più metodi su di esso:

new Quadrato().x(100).y(100).dimensione(50).contorno("red").riempi("blue").disegna();

Si noti che this è una parola chiave, non una variabile o nome di proprietà. La sintassi JavaScript non permette di assegnare un valore a this.

La parola chiave this non ha lo scope come le variabili, e, eccetto per le arrow function, le funzioni annidate non ereditano il valore this della funzione contenitrice. Se una funzione annidata è invocata come un metodo, il suo valore this è l'oggetto su cui è stata invocata. Se una funzione annidata (che non è un'arrow function) è invocata come una funzione, allora il suo valore this sarà o l'oggetto globale (modalità non-strict) o undefined (modalità strict). È un errore comune assumere che una funzione annidata definita all'interno di un metodo e invocata come una funzione possa usare this per ottenere il contesto di invocazione del metodo. Il codice seguente dimostra il problema:

let o = {                 // Un oggetto o.
    m: function() {       // Metodo m dell'oggetto.
        let self = this;  // Salva il valore "this" in una variabile.
        this === o        // => true: "this" è l'oggetto o.
        f();              // Ora chiama la funzione helper f().

        function f() {    // Una funzione annidata f
            this === o    // => false: "this" è globale o undefined
            self === o    // => true: self è il valore "this" esterno.
        }
    }
};
o.m();                    // Invoca il metodo m sull'oggetto o.

Dentro la funzione annidata f(), la parola chiave this non è uguale all'oggetto o. Questo è ampiamente considerato un difetto nel linguaggio JavaScript, ed è importante esserne consapevoli. Il codice sopra dimostra una soluzione comune. All'interno del metodo m, assegniamo il valore this a una variabile self, e all'interno della funzione annidata f, possiamo usare self invece di this per riferirci all'oggetto contenitore.

In ES6 e successivi, un'altra soluzione a questo problema è convertire la funzione annidata f in un'arrow function, che erediterà propriamente il valore this:

const f = () => {
    this === o  // true, dato che le arrow function ereditano this
};

Le funzioni definite come espressioni invece che come dichiarazioni non sono hoisted, quindi per far funzionare questo codice, la definizione della funzione per f dovrà essere spostata all'interno del metodo m in modo che appaia prima di essere invocata.

Un'altra soluzione è invocare il metodo bind() della funzione annidata per definire una nuova funzione che è implicitamente invocata su un oggetto specificato:

const f = (function() {
    this === o  // true, dato che abbiamo legato questa funzione al this esterno
}).bind(this);

Studieremo nel dettaglio il metodo bind() nelle prossime lezioni, ma per ora è sufficiente sapere che bind() restituisce una nuova funzione che ha il valore this impostato sul valore specificato. In questo caso, il valore di this sarà l'oggetto o, e quindi la funzione annidata f potrà usare this per riferirsi a quell'oggetto.