W3docs

Decorator e inoltro delle chiamate in JavaScript: call e apply

Impara a scrivere funzioni decorator in JavaScript e a inoltrare le chiamate con func.call e func.apply, inclusi caching decorator, bind e method borrowing.

Un decorator è una funzione wrapper: prende un'altra funzione e restituisce una nuova funzione che aggiunge comportamenti — logging, caching, timing, controlli di accesso — attorno all'originale, senza modificarne il codice. Per costruire decorator che funzionino con qualsiasi funzione, occorre un modo affidabile di invocare una funzione con un this scelto e un insieme di argomenti scelto. Questo è esattamente ciò che func.call e func.apply offrono.

Questo capitolo tratta le funzioni decorator (wrapper), l'inoltro di this e degli argomenti con call/apply, il ripristino del contesto perso con bind e il method borrowing.

Nota: Questo riguarda i decorator di funzione — il pattern comune disponibile in JavaScript standard oggi. I più recenti decorator di classe con prefisso @ sono una funzionalità separata e più avanzata (attualmente una proposta Stage 3 che richiede un transpiler) e non vengono trattati qui.

Cos'è un decorator

Un decorator è una funzione che avvolge una funzione target e restituisce un sostituto con comportamento aggiuntivo. Poiché il wrapper ha la stessa interfaccia esterna, i chiamanti non devono cambiare nulla.

function sum(a, b) {
  return a + b;
}

function logged(func) {
  return function (a, b) {
    console.log(`calling with ${a}, ${b}`);
    return func(a, b);
  };
}

const loggedSum = logged(sum);
console.log(loggedSum(2, 3));
// calling with 2, 3
// 5

Il wrapper è riutilizzabile, mantiene l'originale intatto e può essere composto. Il limite dell'esempio sopra è che gestisce solo una funzione con esattamente due argomenti e nessun this. Per avvolgere qualsiasi funzione, occorre inoltrare la chiamata.

Un caching decorator

Un decorator molto comune in pratica mette in cache i risultati affinché una funzione costosa venga eseguita una sola volta per ogni input. Provalo:

javascript— editable

Questo funziona per una funzione standalone. Ma nel momento in cui slow è un metodo che usa this, chiamare func(x) lo rompe — il wrapper perde il contesto dell'object. È qui che entrano in gioco call e apply.

Inoltro della chiamata: call e apply

call e apply invocano entrambi una funzione con un this esplicitamente scelto. Differiscono solo nel modo in cui vengono passati gli argomenti:

  • func.call(thisArg, arg1, arg2, ...) — argomenti elencati singolarmente.
  • func.apply(thisArg, argsArray) — argomenti come un singolo array (o array-like).

call

javascript— editable

apply

javascript— editable

Queste due chiamate sono equivalenti:

func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]);

Usa call quando conosci gli argomenti singolarmente; usa apply quando li hai già in un array. Con la sintassi spread (func.call(obj, ...args)) la distinzione spesso scompare — vedi Parametri rest e sintassi spread.

Inoltro di this con call

Ora possiamo correggere il caching decorator per i metodi. All'interno del wrapper, this è l'object su cui il metodo è stato chiamato, quindi lo inoltriamo con func.call(this, x):

javascript— editable

Senza func.call(this, x), la chiamata interna sarebbe func(x) e this andrebbe perso, quindi this.someMethod() fallirebbe.

Inoltro di tutti gli argomenti con apply

Per un metodo con più argomenti, si inoltrano tutti gli argomenti in una sola volta. Il wrapper non sa quanti ce ne siano, quindi li legge da arguments e li passa tutti tramite func.apply(this, arguments):

javascript— editable

Passare this e arguments direttamente è chiamato call forwarding: il wrapper si comporta esattamente come l'originale, con l'aggiunta di logica extra attorno ad esso.

Method borrowing

La funzione hash qui sopra usa un trucco. arguments è array-like (ha indici e length) ma non è un vero array, quindi non ha join. Invece di convertirlo, prendiamo in prestito il metodo dell'array:

function hash(args) {
  return [].join.call(args, ',');
}
console.log(hash([3, 5])); // "3,5"

[].join è Array.prototype.join. Chiamarlo con args come this esegue la logica di join sul valore array-like. Il method borrowing consente di riutilizzare i metodi built-in su object che non sono di quel tipo.

bind e la perdita del contesto

call e apply invocano immediatamente. bind invece restituisce una nuova funzione con this fissato in modo permanente — utile quando la chiamata avviene in seguito (una callback, un event handler, un setTimeout).

Il problema che bind risolve è la perdita del contesto: staccare un metodo dal suo object fa sì che this non punti più ad esso.

javascript— editable

Per un approfondimento su come correggere il contesto nelle callback e sulla differenza tra bind, le arrow function e call/apply, vedi Function binding.

Quando usare quale

ObiettivoUsa
Chiamare subito con this scelto, argomenti elencati singolarmentefunc.call(thisArg, a, b)
Chiamare subito con this scelto, argomenti già in un arrayfunc.apply(thisArg, args)
Ottenere una funzione da chiamare in seguito con this fissofunc.bind(thisArg)
Riutilizzare un metodo built-in su un object array-likeprendilo in prestito: [].method.call(obj, …)

Conclusione

I decorator avvolgono una funzione per aggiungere comportamento senza modificarla. Per far funzionare un wrapper con qualsiasi funzione — inclusi i metodi — si inoltra la chiamata originale con func.call(this, ...) o func.apply(this, arguments), si usa bind quando la chiamata è differita, e si prendono in prestito i metodi built-in quando un object è solo array-like. Insieme, questi strumenti consentono di creare astrazioni riutilizzabili e context-safe come il caching decorator visto sopra.

Letture correlate: Metodi degli object, "this", Function object, NFE e Function binding.

Esercitazione

Pratica
Quali affermazioni descrivono accuratamente l'uso e le differenze tra i metodi `call` e `apply` in JavaScript?
Quali affermazioni descrivono accuratamente l'uso e le differenze tra i metodi `call` e `apply` in JavaScript?
Was this page helpful?