Proprietà, Metodi e Costruttori delle Funzioni in JavaScript
- Le funzioni in JavaScript hanno proprietà e metodi che possono essere utilizzati per ottenere informazioni sulla funzione e modificarne il comportamento.
- La proprietà
length
indica il numero di parametri dichiarati nella funzione. - La proprietà
name
fornisce il nome della funzione, utile per il debug. - I metodi
call()
eapply()
permettono di invocare una funzione con un contesto specifico, modificando il valore dithis
. - Il metodo
bind()
crea una nuova funzione con un contesto specifico e può applicare parzialmente gli argomenti. - Il metodo
toString()
restituisce una rappresentazione testuale della funzione, utile per il debug. - Il costruttore
Function()
consente di creare funzioni dinamicamente, ma non utilizza lo scoping lessicale come le funzioni normali.
Proprietà, Metodi e Costruttore delle Funzioni
Si è visto che le funzioni sono valori nei programmi JavaScript. L'operatore typeof
restituisce la stringa "function" quando applicato a una funzione, ma le funzioni sono in realtà un tipo specializzato di oggetto JavaScript. Poiché le funzioni sono oggetti, possono avere proprietà e metodi, proprio come qualsiasi altro oggetto. Esiste anche un costruttore Function()
per creare nuovi oggetti funzione. Le sottosezioni che seguono documentano le proprietà length
, name
e prototype
; i metodi call()
, apply()
, bind()
e toString()
; e il costruttore Function()
.
Di seguito sono riportate le proprietà che una funzione può avere:
-
La Proprietà
length
La proprietà di sola lettura
length
di una funzione specifica l'arità della funzione, il numero di parametri che dichiara nella sua lista di parametri, che è solitamente il numero di argomenti che la funzione si aspetta. Se una funzione ha un parametro rest, quel parametro non viene contato ai fini di questa proprietàlength
. -
La Proprietà
name
La proprietà
name
di sola lettura di una funzione specifica il nome che è stato utilizzato quando la funzione è stata definita, se è stata definita con un nome, o il nome della variabile o proprietà a cui un'espressione di funzione senza nome è stata assegnata quando è stata creata per la prima volta. Questa proprietà è principalmente utile quando si scrivono messaggi di debug o di errore. -
La Proprietà
prototype
Tutte le funzioni, eccetto le arrow function, hanno una proprietà
prototype
che si riferisce a un oggetto conosciuto come oggetto prototipo. Ogni funzione ha un oggetto prototipo diverso. Quando una funzione viene utilizzata come costruttore, l'oggetto appena creato eredita proprietà dall'oggetto prototipo.
I Metodi call
e apply
call()
e apply()
consentono di invocare indirettamente una funzione come se fosse un metodo di qualche altro oggetto. Il primo argomento sia di call()
che di apply()
è l'oggetto su cui la funzione deve essere invocata; questo argomento rappresenta il contesto di invocazione e diventa il valore della parola chiave this
all'interno del corpo della funzione. Per invocare la funzione f()
come un metodo dell'oggetto o
(non passando argomenti), si può usare sia call()
che apply()
:
f.call(o);
f.apply(o);
Entrambe queste righe di codice sono simili al seguente (che presuppone che o
non abbia già una proprietà chiamata m
):
// Rende f un metodo temporaneo di o.
o.m = f;
// Lo invoca, non passando argomenti.
o.m();
// Rimuove il metodo temporaneo.
delete o.m;
Si ricordi che le arrow function ereditano il valore this
del contesto in cui sono definite. Questo non può essere sovrascritto con i metodi call()
e apply()
. Se si chiama uno di questi metodi su un'arrow function, il primo argomento viene effettivamente ignorato.
Tutti gli argomenti di call()
dopo il primo argomento del contesto di invocazione sono i valori che vengono passati alla funzione che viene invocata (e questi argomenti non vengono ignorati per le arrow function). Ad esempio, per passare due numeri alla funzione f()
e invocarla come se fosse un metodo dell'oggetto o
, si potrebbe usare codice come questo:
f.call(o, 1, 2);
Il metodo apply()
è come il metodo call()
, eccetto che gli argomenti da passare alla funzione sono specificati come un array:
f.apply(o, [1, 2]);
Se una funzione è definita per accettare un numero arbitrario di argomenti, il metodo apply()
consente di invocare quella funzione sui contenuti di un array di lunghezza arbitraria. In ES6 e successivi, si può semplicemente usare lo spread operator, ma si potrebbe vedere codice ES5 che usa apply()
invece. Ad esempio, per trovare il numero più grande in un array di numeri senza usare lo spread operator, si potrebbe usare il metodo apply()
per passare gli elementi dell'array alla funzione Math.max()
:
let piuGrande = Math.max.apply(Math, arrayDiNumeri);
La funzione trace()
definita di seguito è simile alla funzione timed()
, ma funziona per i metodi invece che per le funzioni. Usa il metodo apply()
invece di uno spread operator, e facendo questo, è in grado di invocare il metodo wrappato con gli stessi argomenti e lo stesso valore this
del metodo wrapper:
// Sostituisce il metodo chiamato m dell'oggetto o con una versione che registra
// messaggi prima e dopo aver invocato il metodo originale.
function trace(o, m) {
// Ricorda il metodo originale nella closure.
let originale = o[m];
// Ora definisce il nuovo metodo.
o[m] = function(...args) {
// Messaggio di log.
console.log(new Date(), "Entrando:", m);
// Invoca l'originale.
let risultato = originale.apply(this, args);
// Messaggio di log.
console.log(new Date(), "Uscendo:", m);
// Restituisce il risultato.
return risultato;
};
}
Il Metodo bind
Lo scopo principale di bind()
è quello di legare una funzione a un oggetto. Quando si invoca il metodo bind()
su una funzione f
e si passa un oggetto o
, il metodo restituisce una nuova funzione. Invocare la nuova funzione (come una funzione) invoca la funzione originale f
come metodo di o
. Qualsiasi argomento si passi alla nuova funzione viene passato alla funzione originale. Per esempio:
// Questa funzione deve essere legata
function f(y) {
return this.x + y;
}
// Un oggetto a cui legheremo
let o = { x: 1 };
// Chiamare g(x) invoca f() su o
let g = f.bind(o);
// => 3
g(2);
// Invoca g() come metodo di questo oggetto
let p = { x: 10, g };
// => 3: g è ancora legata a o, non a p.
p.g(2);
Le arrow function ereditano il loro valore this
dall'ambiente in cui sono definite, e quel valore non può essere sovrascritto con bind()
, quindi se la funzione f()
nel codice precedente fosse stata definita come arrow function, il binding non funzionerebbe. Il caso d'uso più comune per chiamare bind()
è rendere le funzioni non-arrow comportarsi come arrow function, tuttavia, quindi questa limitazione sul binding delle arrow function non è un problema in pratica.
Il metodo bind()
fa più che semplicemente legare una funzione a un oggetto, tuttavia. Può anche eseguire applicazione parziale: qualsiasi argomento si passi a bind()
dopo il primo viene legato insieme al valore this
. Questa funzionalità di applicazione parziale di bind()
funziona con le arrow function. L'applicazione parziale è una tecnica comune nella programmazione funzionale e viene talvolta chiamata currying. Ecco alcuni esempi del metodo bind()
usato per l'applicazione parziale:
// Restituisce la somma di 2 argomenti
let somma = (x, y) => x + y;
// Lega il primo argomento a 1
let successore = somma.bind(null, 1);
// => 3: x è legata a 1, e si passa 2 per l'argomento y
successore(2);
function f(y, z) {
return this.x + y + z;
}
// Lega this e y
let g = f.bind({x: 1}, 2);
// => 6: this.x è legata a 1, y è legata a 2 e z è 3
g(3);
La proprietà name
della funzione restituita da bind()
è la proprietà name della funzione su cui bind()
è stato chiamato, preceduta dalla parola "bound"
.
Il Metodo toString
Come tutti gli oggetti JavaScript, le funzioni hanno un metodo toString()
. La specifica ECMAScript richiede che questo metodo restituisca una stringa che segua la sintassi dell'istruzione di dichiarazione di funzione. In pratica, la maggior parte (ma non tutte) delle implementazioni di questo metodo toString()
restituisce il codice sorgente completo per la funzione. Le funzioni integrate tipicamente restituiscono una stringa che include qualcosa come "[native code]"
come corpo della funzione.
Il Costruttore Function
Poiché le funzioni sono oggetti, esiste un costruttore Function()
che può essere utilizzato per creare nuove funzioni:
const f = new Function("x", "y", "return x*y;");
Questa riga di codice crea una nuova funzione che è più o meno equivalente a una funzione definita con la sintassi familiare:
const f = function(x, y) {
return x * y;
};
Il costruttore Function()
si aspetta qualsiasi numero di argomenti stringa. L'ultimo argomento è il testo del corpo della funzione; può contenere istruzioni JavaScript arbitrarie, separate l'una dall'altra da punti e virgola. Tutti gli altri argomenti del costruttore sono stringhe che specificano i nomi dei parametri per la funzione. Se si sta definendo una funzione che non accetta argomenti, si passerebbe semplicemente una singola stringa---il corpo della funzione---al costruttore.
Si noti che al costruttore Function()
non viene passato alcun argomento che specifichi un nome per la funzione che crea. Come i letterali di funzione, il costruttore Function()
crea funzioni anonime.
Ci sono alcuni punti che è importante comprendere riguardo al costruttore Function()
:
- Il costruttore
Function()
consente alle funzioni JavaScript di essere create dinamicamente e compilate a runtime. - Il costruttore
Function()
analizza il corpo della funzione e crea un nuovo oggetto funzione ogni volta che viene chiamato. Se la chiamata al costruttore appare all'interno di un ciclo o all'interno di una funzione chiamata frequentemente, questo processo può essere inefficiente. Al contrario, le funzioni annidate e le espressioni di funzione che appaiono all'interno dei cicli non vengono ricompilate ogni volta che vengono incontrate. -
Un ultimo, molto importante punto riguardo al costruttore
Function()
è che le funzioni che crea non utilizzano lo scoping lessicale; invece, sono sempre compilate come se fossero funzioni di livello superiore, come dimostra il seguente codice:let ambito = "globale"; function costruisciFunzione() { let ambito = "locale"; // Non cattura l'ambito locale! return new Function("return ambito"); } // Questa riga restituisce "globale" perché la funzione restituita dal // costruttore Function() non utilizza l'ambito locale. // => "globale" costruisciFunzione()();
Il costruttore Function()
è meglio pensato come una versione con ambito globale di eval()
(vedi §4.12.2) che definisce nuove variabili e funzioni nel suo proprio ambito privato. Probabilmente non si avrà mai bisogno di utilizzare questo costruttore nel proprio codice.