IIFE JavaScript

Edoardo Midali
Edoardo Midali

Le IIFE (Immediately Invoked Function Expressions) sono funzioni JavaScript che vengono eseguite immediatamente dopo essere state definite. Questo pattern è stato fondamentale nello sviluppo JavaScript prima dell’introduzione dei moduli ES6 e rimane utile in molte situazioni per creare scope isolati e prevenire l’inquinamento del namespace globale.

Cos’è una IIFE

Una IIFE è una funzione che viene dichiarata e invocata nello stesso momento. La caratteristica principale è che crea uno scope privato dove le variabili e le funzioni non interferiscono con il codice circostante:

// Sintassi base di una IIFE
(function () {
  console.log("Questa funzione viene eseguita immediatamente!");
})();

// Equivale a:
function miaFunzione() {
  console.log("Questa funzione viene eseguita immediatamente!");
}
miaFunzione();
// Ma la IIFE non "inquina" il namespace globale con il nome della funzione

Le parentesi attorno alla function declaration sono necessarie per trasformarla in un’espressione, perché JavaScript non permette di invocare direttamente una function declaration.

Sintassi e Varianti

Sintassi Classica

// Variante 1: parentesi attorno a tutto
(function () {
  console.log("IIFE variante 1");
})();

// Variante 2: parentesi solo attorno alla funzione
(function () {
  console.log("IIFE variante 2");
})();

// Con parametri
(function (nome, età) {
  console.log(`Ciao ${nome}, hai ${età} anni`);
})("Mario", 30);

Entrambe le varianti sono valide e funzionalmente identiche. La scelta è spesso questione di preferenza del team o standard di codifica.

Con Arrow Functions

// IIFE con arrow function
(() => {
  console.log("Arrow IIFE");
})();

// Con parametri
((nome, età) => {
  console.log(`Ciao ${nome}, hai ${età} anni`);
})("Mario", 30);

// IIFE che restituisce un valore
const risultato = (() => {
  const a = 5;
  const b = 10;
  return a + b;
})();

console.log(risultato); // 15

Vantaggi delle IIFE

Incapsulamento e Privacy

Le IIFE creano uno scope privato dove le variabili non sono accessibili dall’esterno:

// Senza IIFE - variabili globali
var contatore = 0;
var incrementa = function () {
  contatore++;
  return contatore;
};

console.log(contatore); // 0 - accessibile globalmente!

// Con IIFE - incapsulamento
const modelloContatore = (function () {
  let contatore = 0; // Privata!

  return {
    incrementa: function () {
      contatore++;
      return contatore;
    },
    ottieni: function () {
      return contatore;
    },
    reset: function () {
      contatore = 0;
    },
  };
})();

console.log(modelloContatore.incrementa()); // 1
console.log(modelloContatore.ottieni()); // 1
// console.log(contatore); // ReferenceError - non accessibile!

Prevenzione Conflitti di Nomi

// Libreria A
(function () {
  var utilità = {
    formatta: function (testo) {
      return testo.toUpperCase();
    },
  };

  window.LibreriaA = utilità;
})();

// Libreria B - può usare lo stesso nome senza conflitti
(function () {
  var utilità = {
    formatta: function (testo) {
      return testo.toLowerCase();
    },
  };

  window.LibreriaB = utilità;
})();

// Uso senza conflitti
console.log(LibreriaA.formatta("ciao")); // "CIAO"
console.log(LibreriaB.formatta("CIAO")); // "ciao"

Pattern Comuni con IIFE

Module Pattern

Prima dei moduli ES6, le IIFE erano il modo principale per creare moduli:

const calcolatore = (function () {
  // Variabili private
  let memoria = 0;
  let storia = [];

  // Funzioni private
  function registraOperazione(operazione, risultato) {
    storia.push({ operazione, risultato, timestamp: Date.now() });
  }

  // API pubblica
  return {
    somma: function (a, b) {
      const risultato = a + b;
      registraOperazione(`${a} + ${b}`, risultato);
      return risultato;
    },

    sottrazione: function (a, b) {
      const risultato = a - b;
      registraOperazione(`${a} - ${b}`, risultato);
      return risultato;
    },

    memorizza: function (valore) {
      memoria = valore;
    },

    richiama: function () {
      return memoria;
    },

    ottieniStoria: function () {
      return [...storia]; // Copia per prevenire modifiche esterne
    },
  };
})();

// Utilizzo
console.log(calcolatore.somma(5, 3)); // 8
calcolatore.memorizza(10);
console.log(calcolatore.richiama()); // 10
console.log(calcolatore.ottieniStoria()); // Array con operazioni

Namespace Pattern

// Creazione di un namespace globale organizzato
window.MiaApp = (function () {
  // Configurazione privata
  const config = {
    apiUrl: "https://api.esempio.com",
    versione: "1.0.0",
  };

  return {
    // Moduli pubblici
    utils: {
      formatDate: function (date) {
        return date.toLocaleDateString("it-IT");
      },

      validateEmail: function (email) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
      },
    },

    api: {
      chiamata: function (endpoint) {
        return fetch(config.apiUrl + endpoint);
      },
    },

    getVersion: function () {
      return config.versione;
    },
  };
})();

// Utilizzo
console.log(MiaApp.utils.formatDate(new Date()));
console.log(MiaApp.getVersion());

Inizializzazione Condizionale

// Inizializzazione che dipende da condizioni
(function () {
  // Verifica supporto per una feature
  if ("geolocation" in navigator) {
    console.log("Geolocalizzazione supportata");

    // Inizializza servizi di geolocalizzazione
    navigator.geolocation.getCurrentPosition(function (position) {
      console.log("Posizione ottenuta:", position.coords);
    });
  } else {
    console.log("Geolocalizzazione non supportata");
    // Fallback o messaggio utente
  }
})();

// Inizializzazione al caricamento DOM
(function () {
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", inizializza);
  } else {
    inizializza();
  }

  function inizializza() {
    console.log("DOM pronto, inizializzazione completata");
    // Logica di inizializzazione
  }
})();

IIFE vs Soluzioni Moderne

Prima dei Moduli ES6

// Pattern vecchio con IIFE
const matematica = (function () {
  const PI = 3.14159;

  function areaCircolo(raggio) {
    return PI * raggio * raggio;
  }

  return {
    areaCircolo: areaCircolo,
    PI: PI,
  };
})();

Con Moduli ES6

// modulo-matematica.js
const PI = 3.14159;

export function areaCircolo(raggio) {
  return PI * raggio * raggio;
}

export { PI };

// main.js
import { areaCircolo, PI } from "./modulo-matematica.js";

Quando Usare le IIFE Oggi

Isolamento rapido: Quando hai bisogno di creare rapidamente uno scope isolato senza creare file separati.

Inizializzazione una tantum: Per codice che deve essere eseguito immediatamente e non ha bisogno di essere richiamato.

Compatibilità: In ambienti dove i moduli ES6 non sono disponibili o pratici.

Testing e debugging: Per isolare temporaneamente porzioni di codice durante lo sviluppo.

// Esempio moderno: configurazione rapida
(function () {
  // Configurazione specifica per questa pagina
  const tema = localStorage.getItem("tema") || "chiaro";
  document.body.className = `tema-${tema}`;

  // Event listener specifici per questa pagina
  document
    .getElementById("toggle-tema")
    ?.addEventListener("click", function () {
      const nuovoTema = tema === "chiaro" ? "scuro" : "chiaro";
      localStorage.setItem("tema", nuovoTema);
      location.reload();
    });
})();

Le IIFE rimangono uno strumento utile nel toolkit JavaScript moderno, specialmente per situazioni che richiedono incapsulamento immediato o inizializzazione once-off. Anche se i moduli ES6 hanno sostituito molti dei loro usi tradizionali, comprendere le IIFE è importante per mantenere e comprendere codice legacy e per situazioni specifiche dove offrono la soluzione più semplice.