![]() |
Quando si lavora con funzioni complesse o ripetitive in JavaScript, ottimizzare le prestazioni diventa fondamentale. In questo articolo scoprirai cos’è la memoization, come funziona e perché è una tecnica così efficace per migliorare la velocità delle tue applicazioni.
Cos'è la Memoization?
La memoization è una tecnica di ottimizzazione che consiste nel memorizzare i risultati di chiamate a funzione costose, in modo che successive invocazioni con gli stessi argomenti possano restituire immediatamente il valore precedentemente calcolato, evitando ricalcoli inutili.
In pratica, memoizzare una funzione significa aggiungere una cache che salva i risultati in base ai parametri ricevuti. Se una funzione viene chiamata più volte con gli stessi argomenti, si legge il valore dalla cache anziché rieseguire l'intera funzione.
Perché Usare la Memoization?
Le principali situazioni in cui la memoization è utile includono:
- Calcoli matematici ricorsivi pesanti (es. Fibonacci, fattoriali).
- Funzioni pure con input limitati e ripetuti.
- Rendering complessi in ambienti React o animazioni.
- Trasformazioni su dati immutabili o grandi dataset.
Benefici chiave:
- ✅ Prestazioni migliorate: Riduce il numero di esecuzioni ridondanti.
- ✅ Risposta immediata: Migliora la reattività delle applicazioni.
- ✅ Riduzione del carico: Ottimizza l’uso della CPU, utile per funzioni CPU-bound.
- ✅ Ideale per funzioni deterministiche: Se una funzione restituisce sempre lo stesso output per un dato input, memoizzarla è sicuro ed efficace.
Come Funziona la Memoization in JavaScript?
Vediamo un esempio pratico di funzione senza memoization e poi una versione ottimizzata.
Funzione senza memoization
function slowFibonacci(n) {
if (n <= 1) return n;
return slowFibonacci(n - 1) + slowFibonacci(n - 2);
}
console.log(slowFibonacci(40)); // Estremamente lento
Questa funzione è un classico esempio di ricorsione esponenziale, dove ogni chiamata genera due nuove chiamate. Il tempo di esecuzione cresce molto rapidamente.
Versione memoizzata
function memoizedFibonacci() {
const cache = {};
return function fib(n) {
if (n in cache) {
return cache[n];
}
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
const fibonacci = memoizedFibonacci();
console.log(fibonacci(40)); // Molto più veloce
In questa versione, la funzione fib salva i risultati in una cache (cache[n]). Ogni risultato viene calcolato una sola volta, poi riutilizzato.
Esempio di Memoization con chiamate API
La memoization può essere utile anche per evitare chiamate API ridondanti. Se una determinata richiesta API restituisce sempre lo stesso risultato per gli stessi parametri, possiamo salvare il risultato nella cache e riutilizzarlo nelle chiamate successive.
function memoizeApiCall(apiFunction) {
const cache = new Map();
return async function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key); // ritorna dalla cache
}
const result = await apiFunction(...args);
cache.set(key, result);
return result;
};
}
// Esempio di chiamata API
async function fetchUserData(userId) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) throw new Error("Errore nella richiesta");
return response.json();
}
// Versione memoizzata
const memoizedFetchUserData = memoizeApiCall(fetchUserData);
// Uso
memoizedFetchUserData(1).then(data => console.log("Prima chiamata:", data));
memoizedFetchUserData(1).then(data => console.log("Seconda chiamata dalla cache:", data));
In questo esempio, memoizeApiCall avvolge una funzione API asincrona e salva il risultato per ogni set di parametri (qui, l'userId). La seconda chiamata con lo stesso ID non genera una nuova richiesta HTTP, ma restituisce subito il risultato dalla cache.
⚠️ Nota: La cache è in memoria, quindi si resetta al reload della pagina. Per cache persistenti potresti considerare localStorage o IndexedDB.
Implementare una Funzione Generica di Memoization
La memoization può essere facilmente generalizzata in JavaScript per qualsiasi funzione con input deterministici:
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Esempio d'uso
function add(a, b) {
console.log("Computing...");
return a + b;
}
const memoizedAdd = memoize(add);
console.log(memoizedAdd(3, 4)); // Computing... 7
console.log(memoizedAdd(3, 4)); // From cache: 7
Questa versione usa Map e JSON.stringify per creare una chiave unica basata sugli argomenti. È una tecnica molto comune e riutilizzabile.
Memoization e Funzioni Non Pure
⚠️ È importante ricordare che la memoization funziona solo con funzioni pure, ovvero:
- Stesso output per stessi input.
- Nessun effetto collaterale (side effect).
Funzioni che dipendono da stato globale, input/output, o interazioni con l’ambiente non sono adatte alla memoization, perché il caching potrebbe produrre risultati non aggiornati o errati.
Memoization in ambienti moderni (React, Lodash, etc.)
In React
Nel mondo React, memoization è usata per:
- Evitare ricalcoli costosi (con
useMemo). - Evitare ricostruzione di funzioni (con
useCallback). - Evitare rendering inutili (con
React.memo).
const memoizedValue = useMemo(() => expensiveFunction(input), [input]);
Lodash
La libreria Lodash include una funzione _.memoize():
const _ = require('lodash');
const memoizedFn = _.memoize(someExpensiveFunction);
Lodash gestisce internamente la cache ed è utile per applicazioni più complesse o produzione.
Limiti e Considerazioni
- Uso della memoria: Ogni risultato è memorizzato; se gli input sono molti o unici, la cache può crescere molto.
- Cache invalidation: Non è facile gestire quando “scadere” o aggiornare la cache.
- Argomenti complessi:
JSON.stringifyha limiti con oggetti ciclici o funzioni come argomenti. - Spazio/tempo trade-off: Aumenti di prestazioni a costo di uso di memoria.
Conclusioni
La memoization è una potente tecnica di ottimizzazione per le funzioni deterministiche, specialmente quelle costose da eseguire ripetutamente. In JavaScript, è semplice da implementare ed è molto utile in applicazioni web moderne, soprattutto se combinate con React o librerie come Lodash.
💡 Best Practice: Memoizza solo funzioni pure, usa strumenti pronti quando disponibili (React, Lodash), e tieni d’occhio la gestione della cache.
Follow me #techelopment
Official site: www.techelopment.it
facebook: Techelopment
instagram: @techelopment
X: techelopment
Bluesky: @techelopment
whatsapp: Techelopment
youtube: @techelopment
