Introduzione
Le funzioni JavaScript non servono soltanto a raggruppare istruzioni riutilizzabili. Se combinate con attenzione, possono diventare piccoli componenti specializzati, facili da testare e da riutilizzare in contesti diversi. In questo tutorial costruiremo una pipeline di validazione per controllare i dati di un modulo di registrazione.
Il progetto ci permetterà di confrontare tre modi di definire funzioni:
- Dichiarazioni di funzione, utili per definire operazioni principali e leggibili.
- Espressioni di funzione, adatte quando vogliamo assegnare una funzione a una variabile o passarla come configurazione.
- Arrow functions, concise e particolarmente comode per funzioni brevi e callback.
Inoltre useremo le closure per creare regole di validazione configurabili. Una closure consente a una funzione interna di ricordare i valori presenti nel suo ambiente esterno, anche dopo la fine dell’esecuzione della funzione esterna.
Codice completo
// 1. Dichiarazione di funzione:
// controlla che un valore sia una stringa non vuota.
function isNonEmpty(value) {
return typeof value === "string" && value.trim().length > 0;
}
// 2. Espressione di funzione:
// crea una regola configurabile per la lunghezza minima.
const hasMinLength = function (minLength) {
return function (value) {
return typeof value === "string" && value.trim().length >= minLength;
};
};
// 3. Arrow function:
// verifica che una stringa contenga un carattere specifico.
const includesCharacter = (character) => (value) => {
return typeof value === "string" && value.includes(character);
};
// La funzione restituita da hasMinLength ricorda il valore 8.
const hasPasswordLength = hasMinLength(8);
// La funzione restituita da includesCharacter ricorda il carattere "@"
const containsAtSymbol = includesCharacter("@");
// Esegue tutte le regole in ordine e restituisce il primo errore.
function validateField(value, rules) {
for (const rule of rules) {
const result = rule(value);
if (result !== true) {
return result;
}
}
return true;
}
// Espressione di funzione usata come configurazione.
const emailRules = [
(value) => {
if (!isNonEmpty(value)) {
return "Il campo email è obbligatorio.";
}
return true;
},
(value) => {
if (!containsAtSymbol(value)) {
return "L´email deve contenere il carattere @.";
}
return true;
}
];
const passwordRules = [
(value) => {
if (!isNonEmpty(value)) {
return "La password è obbligatoria.";
}
return true;
},
(value) => {
if (!hasPasswordLength(value)) {
return "La password deve contenere almeno 8 caratteri.";
}
return true;
}
];
// Valida un oggetto di dati usando le regole associate a ogni campo.
function validateRegistrationForm(data) {
const errors = {};
const emailError = validateField(data.email, emailRules);
const passwordError = validateField(data.password, passwordRules);
if (emailError !== true) {
errors.email = emailError;
}
if (passwordError !== true) {
errors.password = passwordError;
}
return errors;
}
// Esempi di utilizzo.
const validData = {
email: "[email protected]",
password: "javascript123"
};
const invalidData = {
email: "anna.example.com",
password: "js"
};
console.log(validateRegistrationForm(validData));
// {}
console.log(validateRegistrationForm(invalidData));
// {
// email: "L´email deve contenere il carattere @.",
// password: "La password deve contenere almeno 8 caratteri."
// } Spiegazione
Dichiarazioni di funzione
La funzione isNonEmpty è una dichiarazione di funzione. Questa sintassi è adatta alle funzioni centrali dell’applicazione, perché comunica chiaramente che si tratta di un’operazione con un nome preciso.
Le dichiarazioni di funzione sono inoltre soggette al hoisting: JavaScript rende disponibile la dichiarazione prima dell’esecuzione effettiva del blocco di codice. Tuttavia, è buona pratica definire le funzioni prima del loro utilizzo, così da mantenere il codice più lineare.
Espressioni di funzione
In const hasMinLength = function (minLength) { ... }, la funzione viene assegnata a una costante. Questa è un’espressione di funzione. Il vantaggio principale è che la funzione diventa un valore: può essere assegnata, passata come argomento o restituita da un’altra funzione.
hasMinLength è una funzione fabbrica. Riceve il numero minimo di caratteri e restituisce una nuova funzione che applica proprio quella regola. Quando scriviamo const hasPasswordLength = hasMinLength(8), il valore 8 viene conservato dalla funzione interna tramite una closure.
Arrow functions e funzioni di ordine superiore
La funzione includesCharacter usa due arrow functions annidate. La prima riceve un carattere, mentre la seconda riceve il valore da controllare. Questo stile è utile quando vogliamo creare regole configurabili in modo compatto:
const containsDot = includesCharacter(".");
console.log(containsDot("documento.txt")); // true Una funzione che riceve o restituisce altre funzioni viene chiamata funzione di ordine superiore. Nel nostro esempio, validateField riceve un array di funzioni-regola e le esegue una alla volta.
Il contratto delle regole
Ogni regola segue lo stesso contratto: riceve un valore e restituisce true se la validazione ha successo oppure una stringa contenente il messaggio di errore. Questo rende tutte le regole intercambiabili.
La funzione validateField interrompe il ciclo al primo errore. Questa scelta è utile quando vogliamo mostrare un solo messaggio prioritario per campo. Se invece volessimo raccogliere tutti gli errori, potremmo usare un array e aggiungere ogni risultato negativo.
Best practice
- Mantieni le funzioni focalizzate: una funzione dovrebbe svolgere un compito preciso, come controllare la lunghezza o coordinare le regole.
- Definisci contratti coerenti: tutte le funzioni di validazione devono restituire lo stesso tipo di risultato.
- Usa nomi descrittivi:
hasMinLengthcomunica meglio lo scopo rispetto a un nome generico comecheck. - Preferisci funzioni pure quando possibile: una funzione pura usa soltanto gli argomenti ricevuti e non modifica dati esterni. È più semplice da testare.
- Usa le closure per la configurazione: una funzione come
hasMinLengthevita di duplicare la logica per ogni valore minimo. - Non abusare delle arrow functions: sono ottime per funzioni brevi, ma una dichiarazione con un blocco esplicito può essere più leggibile per logiche articolate.
- Separa validazione e presentazione: le funzioni dovrebbero restituire dati o messaggi, mentre l’interfaccia grafica dovrebbe occuparsi di mostrarli.
- Testa ogni regola in isolamento: verificare singolarmente
isNonEmpty,hasMinLengthevalidateFieldrende più facile individuare gli errori.
Riepilogo
Abbiamo costruito un sistema di validazione composto da funzioni piccole e combinabili. Le dichiarazioni di funzione sono state usate per le operazioni principali, le espressioni di funzione per creare comportamenti configurabili e le arrow functions per regole concise e callback.
Il concetto più importante è la composizione: invece di creare una grande funzione piena di condizioni, definiamo regole indipendenti e le colleghiamo tramite una pipeline. Le closure permettono inoltre di creare funzioni personalizzate, come una regola per una lunghezza minima scelta dinamicamente.
