JavaScript è un linguaggio di programmazione general-purpose, conosciuto soprattutto perché permette di aggiungere comportamento e interattività alle pagine web. Consente, per esempio, di reagire a un clic, aggiornare una parte della pagina senza ricaricarla, validare un modulo, recuperare dati da un server o costruire applicazioni web molto più complesse di una pagina HTML statica.

Nel browser lavora normalmente insieme a HTML e CSS: HTML definisce struttura e contenuti, CSS ne controlla l’aspetto, JavaScript gestisce comportamento e logica. Se vuoi chiarire anche gli altri due livelli, puoi partire dalla guida a HTML e dalla guida ai CSS.

C’è però una distinzione importante che molte introduzioni saltano: il linguaggio non comprende automaticamente tutto ciò che puoi fare nel browser. Le sue caratteristiche fondamentali sono standardizzate come ECMAScript; funzionalità come DOM, fetch(), timer e gestione della pagina appartengono invece alle API fornite dall’ambiente in cui il codice viene eseguito.

Capire questa differenza rende molto più semplice comprendere non solo cos’è JavaScript, ma anche perché lo stesso linguaggio può funzionare nel browser, su un server o in altri runtime.

Cos’è JavaScript

JavaScript è un linguaggio di programmazione ad alto livello, dinamico e multipiattaforma. La sintassi e le caratteristiche fondamentali vengono definite dallo standard ECMAScript, mentre ogni ambiente di esecuzione aggiunge le API necessarie per interagire con ciò che lo circonda.

Nel browser, per esempio, il codice può:

  • leggere e modificare gli elementi di una pagina;
  • reagire a clic, input da tastiera e altri eventi;
  • inviare richieste HTTP;
  • salvare dati localmente;
  • lavorare con audio, video e grafica;
  • eseguire operazioni asincrone;
  • comunicare con servizi remoti.

Fuori dal browser può invece accedere, a seconda del runtime, a file, rete, processi e altre risorse del sistema.

La conseguenza pratica è semplice: JavaScript è il linguaggio; il browser o il runtime stabiliscono quali capacità aggiuntive sono disponibili.

JavaScript, ECMAScript e Web API: qual è la differenza

Questi tre termini vengono spesso usati come se fossero sinonimi, ma indicano livelli differenti.

ECMAScript è lo standard che definisce il linguaggio: sintassi, tipi di dati, funzioni, oggetti, moduli, gestione degli errori e altre caratteristiche fondamentali. La specifica ECMA-262 viene evoluta in modo continuativo attraverso un processo di standardizzazione coordinato da TC39. Puoi consultare la specifica ufficiale ECMA-262 e la panoramica MDN sulle tecnologie JavaScript.

JavaScript è il nome con cui normalmente ci riferiamo al linguaggio implementato nei browser e negli altri ambienti compatibili con ECMAScript.

Le Web API, invece, sono funzionalità messe a disposizione dal browser. Il DOM, per esempio, non fa parte del core ECMAScript. È l’interfaccia attraverso cui uno script può leggere e modificare il documento HTML.

Lo stesso vale per API come:

  • Fetch API;
  • Web Storage;
  • Geolocation;
  • WebSocket;
  • Canvas;
  • Web Workers.

Prendiamo questa istruzione:

const titolo = document.querySelector("h1");

const e la sintassi dell’assegnazione appartengono a ECMAScript. document e querySelector(), invece, sono disponibili perché il browser fornisce il DOM.

Questa distinzione spiega anche perché uno script eseguito in un ambiente server può usare lo stesso linguaggio pur non avendo necessariamente un oggetto document: non esiste una pagina HTML da manipolare.

Schema della relazione tra JavaScript, ECMAScript, Web API del browser e altri runtime
JavaScript mantiene lo stesso linguaggio di base, mentre browser e altri runtime aggiungono API diverse in base all’ambiente di esecuzione.

JavaScript non è Java

Nonostante il nome, JavaScript e Java sono linguaggi differenti.

Hanno una sintassi che in alcuni punti può sembrare simile, ma differiscono per modello di esecuzione, sistema dei tipi, ecosistema e casi d’uso.

Il primo utilizza una tipizzazione dinamica e un modello a prototipi. Java è invece un linguaggio a tipizzazione statica basato principalmente sulle classi.

Il nome è quindi una fonte storica di confusione, non un’indicazione del fatto che JavaScript sia una versione semplificata di Java o che per imparare uno sia necessario conoscere l’altro.

A cosa serve JavaScript oggi

Il caso d’uso più evidente resta il front-end web, dove il linguaggio trasforma un documento HTML da contenuto relativamente statico a interfaccia capace di reagire all’utente e ai dati.

Il suo utilizzo, però, non è più confinato al browser. Lo stesso ecosistema viene impiegato anche per server, strumenti da riga di comando, build tool e altri tipi di applicazioni.

Questo non significa che sia automaticamente la scelta migliore per qualsiasi progetto. Significa che può essere eseguito in ambienti differenti quando esiste un runtime adatto.

Nel browser: interazioni, DOM, eventi e dati

Supponiamo di avere un pulsante che apre un menu. HTML può descrivere il pulsante e il menu, mentre CSS può stabilire come devono apparire. Per aprire il menu quando l’utente fa clic, però, serve una logica.

Lo script può:

  1. individuare il pulsante nel DOM;
  2. ascoltare l’evento click;
  3. modificare una classe CSS o una proprietà dell’elemento;
  4. aggiornare lo stato dell’interfaccia.

Lo stesso schema è alla base di molte funzionalità comuni: filtri, accordion, form interattivi, slider, finestre modali e navigazioni dinamiche.

Il codice può essere utilizzato anche quando la pagina deve comunicare con un server senza effettuare un caricamento completo. Il vecchio termine AJAX descrive proprio questo modello di comunicazione asincrona; oggi l’implementazione può avvenire, per esempio, tramite fetch(). Se vuoi approfondire la logica, trovi un articolo specifico su cos’è AJAX e come funziona.

Una distinzione utile è questa: una pagina non diventa dinamica per magia. Il codice utilizza le API dell’ambiente per modificare uno stato, reagire a un evento o scambiare dati. È il meccanismo che conta.

Fuori dal browser: server e altri runtime JavaScript

Per eseguire JavaScript non è indispensabile avere una pagina web aperta.

Un runtime come Node.js permette di usare il linguaggio lato server e aggiunge API adatte a compiti diversi da quelli del browser: rete, file system, processi e gestione delle richieste HTTP, per esempio.

Questo consente a uno sviluppatore di creare anche:

  • API;
  • web server;
  • applicazioni backend;
  • strumenti da riga di comando;
  • processi di automazione;
  • tool utilizzati durante lo sviluppo.

Node.js utilizza un modello orientato alle operazioni I/O asincrone, particolarmente utile quando un’applicazione deve gestire numerose operazioni di rete o accessi ad altre risorse senza mantenere il thread occupato durante ogni attesa. La documentazione introduttiva ufficiale di Node.js spiega il funzionamento del runtime.

Il punto da ricordare è che browser e Node.js possono eseguire lo stesso linguaggio, ma non forniscono necessariamente le stesse API.

Come funziona JavaScript in una pagina web

Quando il browser incontra del codice deve analizzarlo ed eseguirlo attraverso il proprio motore JavaScript.

Dire semplicemente che si tratta di un linguaggio “interpretato” oggi è troppo riduttivo. I motori moderni adottano tecniche di esecuzione e ottimizzazione differenti e possono includere compilazione just-in-time. Per iniziare non serve conoscere questi dettagli implementativi, ma è utile evitare il vecchio modello mentale secondo cui ogni istruzione viene semplicemente tradotta una riga alla volta.

Dal punto di vista di chi sviluppa, è più utile distinguere tre livelli:

codice → motore JavaScript → ambiente host/runtime.

Il motore implementa il linguaggio. L’ambiente host fornisce ciò che serve per interagire con browser, rete, timer, file o altre risorse.

Motore JavaScript, runtime ed event loop: il modello essenziale

Browser diversi utilizzano motori differenti. Il loro compito è comprendere ed eseguire il linguaggio previsto dalla specifica ECMAScript.

Il motore, però, non lavora da solo. Nel browser collabora con l’ambiente circostante, che mette a disposizione DOM, eventi, timer, rete e altre Web API.

Qui entra in gioco anche il cosiddetto event loop.

JavaScript viene spesso descritto come single-threaded perché un singolo agente esegue un job alla volta. Questo non significa però che il browser debba rimanere fermo mentre aspetta una risposta di rete o un timer.

Immagina questo codice:

console.log("inizio");

setTimeout(() => {
  console.log("timer");
}, 1000);

console.log("fine");

L’output iniziale sarà:

inizio
fine

e successivamente:

timer
Schema dell’event loop JavaScript con call stack, Web API, task queue e callback
Con setTimeout() il timer viene gestito dall’ambiente; quando il callback è pronto entra nella coda e può essere eseguito quando il call stack torna disponibile.

setTimeout() è fornito dall’ambiente. Il callback non viene eseguito immediatamente nello stack corrente: quando può essere eseguito, il relativo lavoro viene messo in coda e sarà processato quando il job precedente sarà terminato.

Anche Promise e operazioni asincrone si inseriscono in questo modello, con alcune differenze nella gestione delle code. Se vuoi approfondire oltre il livello introduttivo, MDN documenta il modello di esecuzione di JavaScript.

La conseguenza pratica è molto importante: un blocco di codice sincrono che impiega troppo tempo può rendere poco reattiva l’interfaccia, perché impedisce al runtime di gestire rapidamente altri lavori come clic, aggiornamenti visivi o scrolling.

Come lavorano insieme HTML, CSS e JavaScript

HTML, CSS e JavaScript non sono tre alternative per fare la stessa cosa. Svolgono ruoli differenti.

Consideriamo questo elemento:

<button id="saluta">Saluta</button>
<p id="messaggio"></p>

HTML stabilisce che esistono un pulsante e un paragrafo.

Possiamo poi usare CSS:

#saluta {
  padding: 10px 16px;
  font-weight: 600;
}

CSS definisce l’aspetto.

Infine aggiungiamo il comportamento:

const pulsante = document.querySelector("#saluta");
const messaggio = document.querySelector("#messaggio");

pulsante.addEventListener("click", () => {
  messaggio.textContent = "Ciao!";
});

Lo script reagisce all’interazione dell’utente.

Questa separazione è utile anche quando il progetto cresce: struttura, presentazione e logica possono evolvere senza essere necessariamente mescolate nello stesso punto.

Un primo esempio JavaScript pratico

Un esempio semplice è più utile di una lunga lista di funzioni astratte.

Creiamo un pulsante che modifica il testo visualizzato nella pagina.

HTML:

<button id="cambia-testo">Cambia testo</button>
<p id="risultato">Testo iniziale</p>

<script defer src="app.js"></script>

Nel file app.js inseriamo:

const pulsante = document.querySelector("#cambia-testo");
const risultato = document.querySelector("#risultato");

pulsante.addEventListener("click", () => {
  risultato.textContent = "Il testo è stato modificato con JavaScript.";
});

Aprendo la pagina, inizialmente viene mostrato “Testo iniziale”. Quando fai clic sul pulsante, lo script modifica il contenuto del paragrafo.

È un esempio elementare, ma contiene già alcuni dei concetti centrali del linguaggio nel browser.

Modificare una pagina quando l’utente fa clic

La prima riga:

const pulsante = document.querySelector("#cambia-testo");

cerca nel DOM l’elemento che ha l’ID cambia-testo e conserva il riferimento nella variabile pulsante.

La stessa cosa avviene per il paragrafo:

const risultato = document.querySelector("#risultato");

Poi registriamo una funzione da eseguire quando si verifica un evento:

pulsante.addEventListener("click", () => {
  risultato.textContent = "Il testo è stato modificato con JavaScript.";
});

addEventListener() permette di associare una funzione a un evento. In questo caso l’evento è il clic.

Quando l’utente preme il pulsante, viene eseguito il callback e textContent modifica il contenuto testuale del paragrafo.

Cosa succede nel codice, passo per passo

L’esempio permette di vedere la catena completa:

HTML crea gli elementi → il browser costruisce il DOM → lo script individua gli elementi → registra un listener → l’utente genera un evento → il callback viene eseguito → il DOM viene modificato.

Questo modello è molto più utile da imparare rispetto alla semplice idea “JavaScript rende le pagine dinamiche”.

Quando capisci questa sequenza, diventa più facile affrontare form, menu, filtri, richieste di rete e componenti più complessi.

Come inserire JavaScript in HTML

Il codice può essere scritto direttamente in un elemento <script> oppure caricato da un file esterno.

Per piccoli esperimenti puoi incontrare codice inline:

<script>
  console.log("Ciao");
</script>

In un progetto reale, però, mantenere gli script in file .js separati tende a rendere più semplice organizzazione, manutenzione e riutilizzo.

Per uno script classico:

<script src="app.js"></script>

L’elemento può essere posizionato in punti differenti del documento, ma il momento in cui viene scaricato ed eseguito può influenzare il parsing HTML e il comportamento della pagina.

File JavaScript esterno e attributo defer

Una soluzione comune per uno script classico che deve lavorare sul DOM è:

<head>
  <script defer src="app.js"></script>
</head>

Con defer il browser può recuperare il file senza interrompere il parsing HTML per il download e ne rimanda l’esecuzione finché il documento non è stato analizzato.

È utile quando il codice deve accedere a elementi presenti nella pagina.

Al contrario, async è più adatto a script indipendenti: vengono recuperati in parallelo ed eseguiti non appena disponibili, senza garantire lo stesso ordine di esecuzione degli script defer.

Non esiste quindi un attributo universalmente “migliore”: la scelta dipende dalle dipendenze dello script e dal momento in cui deve essere eseguito. Il comportamento corrente è descritto nella documentazione MDN dell’elemento script.

Per gli script classici non è necessario scrivere il vecchio:

<script type="text/javascript">

Il tipo può normalmente essere omesso.

Moduli JavaScript con type=”module”

Quando il codice viene diviso in moduli puoi indicarlo esplicitamente:

<script type="module" src="main.js"></script>

Nel file main.js puoi importare funzionalità da altri moduli:

import { saluta } from "./saluti.js";

saluta();

e nel file saluti.js:

export function saluta() {
  console.log("Ciao");
}

I module script hanno un comportamento diverso dagli script classici e la loro elaborazione è già differita. Per questo aggiungere defer a uno script type="module" non produce lo stesso beneficio che ha su uno script classico.

Per chi sta imparando non è indispensabile iniziare subito dividendo ogni esercizio in molti moduli. È però utile sapere che i moduli fanno parte del JavaScript moderno e permettono di suddividere applicazioni più grandi in unità con responsabilità più chiare.

Le basi di JavaScript da conoscere per iniziare

Il modo migliore per imparare JavaScript non è memorizzare tutte le API del browser. Prima serve una base abbastanza solida del linguaggio.

Una progressione sensata comprende:

  1. variabili e valori;
  2. tipi di dati;
  3. operatori;
  4. condizioni;
  5. cicli;
  6. funzioni;
  7. array e oggetti;
  8. DOM ed eventi;
  9. gestione degli errori;
  10. Promise e programmazione asincrona;
  11. moduli.

Una volta compresi questi concetti, framework e librerie diventano molto più facili da leggere perché puoi distinguere ciò che appartiene al linguaggio da ciò che viene aggiunto dallo strumento.

Variabili, tipi, operatori, condizioni e funzioni

Per dichiarare valori che non devono essere riassegnati si usa normalmente const:

const nome = "Luca";

Quando il valore deve essere riassegnato puoi usare let:

let contatore = 0;

contatore = contatore + 1;

Il linguaggio possiede diversi tipi primitivi, tra cui stringhe, numeri, booleani, undefined, null, bigint e symbol, oltre agli oggetti.

Una condizione può essere scritta così:

const eta = 21;

if (eta >= 18) {
  console.log("Maggiorenne");
} else {
  console.log("Minorenne");
}

Una funzione permette invece di racchiudere una parte di logica riutilizzabile:

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

const totale = somma(4, 6);

Oppure con una arrow function:

const somma = (a, b) => a + b;

Non serve imparare immediatamente ogni variante sintattica. All’inizio conta soprattutto capire valori, flusso del programma, funzioni e trasformazione dei dati.

Eventi e codice asincrono: cosa devi capire all’inizio

Quando il codice lavora nel browser, molte operazioni non avvengono immediatamente in sequenza.

Un clic può arrivare tra dieci secondi. Una richiesta di rete può richiedere un tempo imprevedibile. Un timer deve aspettare.

Per questo una parte importante dello sviluppo moderno riguarda le operazioni asincrone.

Con fetch() puoi, per esempio, iniziare una richiesta HTTP:

fetch("/api/prodotti")
  .then(response => response.json())
  .then(dati => {
    console.log(dati);
  })
  .catch(errore => {
    console.error(errore);
  });

La stessa logica può essere scritta con async e await:

async function caricaProdotti() {
  try {
    const response = await fetch("/api/prodotti");
    const dati = await response.json();

    console.log(dati);
  } catch (errore) {
    console.error(errore);
  }
}

await rende il flusso più leggibile, ma non significa che tutto venga trasformato automaticamente in esecuzione parallela. La funzione asincrona sospende quella parte del proprio lavoro mentre attende la Promise e il runtime può continuare a gestire altri job.

È qui che il modello dell’event loop visto in precedenza diventa utile: non è teoria separata dalla pratica, ma la spiegazione di ciò che accade quando inizi a lavorare con eventi, timer, rete e Promise.

Limiti e aspetti da conoscere prima di usare JavaScript

JavaScript è estremamente versatile, ma questa versatilità non elimina i trade-off.

Il problema non è chiedersi se sia “buono” o “cattivo”. Bisogna capire quanto codice è necessario, dove viene eseguito e quale funzione deve svolgere.

Prestazioni e lavoro sul main thread

Il codice può migliorare l’esperienza dell’utente quando evita ricaricamenti inutili o rende un’interfaccia più reattiva. Può però anche peggiorarla se la pagina scarica ed esegue quantità eccessive di script.

Nel browser una lunga operazione eseguita sul thread principale può ritardare:

  • risposta ai clic;
  • scrolling;
  • aggiornamenti dell’interfaccia;
  • rendering.

Quindi non è corretto dire che “JavaScript migliora le prestazioni” come principio generale.

Può abilitare esperienze più efficienti, ma il codice ha un costo di download, parsing ed esecuzione.

Quando un’attività computazionalmente pesante può essere spostata fuori dal main thread, strumenti come i Web Worker possono essere utili. Non sono però una soluzione automatica per qualsiasi problema e non possono manipolare direttamente il DOM come il codice eseguito nel contesto principale della pagina.

Anche dal punto di vista SEO conviene evitare semplificazioni. Google può eseguire JavaScript durante il rendering, ma scansione, rendering e indicizzazione sono fasi distinte. Per contenuti essenziali, un’architettura che consegna informazioni importanti in modo affidabile resta preferibile a una dipendenza inutile dal rendering client-side. Google documenta il processo nella propria guida di base alla SEO per JavaScript.

Sicurezza, compatibilità e accessibilità

Il codice eseguito nel browser è sotto il controllo dell’utente. Per questo la validazione client-side non deve essere considerata una barriera di sicurezza sufficiente.

Puoi usarla per segnalare immediatamente che un campo è vuoto o ha un formato errato, ma il server deve comunque verificare i dati ricevuti.

Lo stesso principio aiuta a capire problemi come il Cross-Site Scripting (XSS): non esiste una singola funzione che “mette in sicurezza” un’applicazione. La prevenzione dipende da come dati non affidabili vengono inseriti nei diversi contesti HTML, script e DOM, oltre che dalle protezioni applicate dall’applicazione e dal browser.

È quindi meglio evitare il vecchio consiglio di considerare funzioni come encodeURIComponent() una soluzione generica contro XSS: quella funzione serve a codificare componenti di un URI, non a sostituire una strategia di output encoding o sanitizzazione adeguata al contesto.

Anche l’accessibilità merita attenzione. Se una funzione può essere realizzata con un elemento HTML semantico, partire dall’HTML corretto è spesso la scelta più robusta.

Un vero <button> è preferibile, per esempio, a un elemento generico reso cliccabile tramite script: possiede già comportamento e semantica utili per tastiera e tecnologie assistive.

Il codice dovrebbe quindi aggiungere comportamento senza cancellare le fondamenta offerte dalla piattaforma web.

Come iniziare a imparare JavaScript

Per imparare JavaScript non hai bisogno di installare subito un framework.

Un browser moderno contiene già quasi tutto ciò che serve per iniziare: puoi creare un file HTML, collegare uno script e utilizzare gli strumenti per sviluppatori.

Aprendo la console del browser puoi provare immediatamente:

2 + 2

oppure:

const nome = "Anna";
console.log(`Ciao ${nome}`);

Da qui puoi passare gradualmente a un piccolo file HTML e sperimentare con DOM ed eventi.

MDN mette a disposizione una guida JavaScript che può essere utilizzata come riferimento tecnico mentre fai pratica.

Dal browser DevTools al primo piccolo progetto

Una progressione efficace potrebbe essere questa:

1. Console del browser

Prova valori, operatori, condizioni e funzioni.

2. File HTML + JavaScript

Collega app.js a una semplice pagina.

3. DOM

Impara a usare selettori come:

document.querySelector()

e a modificare proprietà come:

element.textContent

4. Eventi

Lavora con:

element.addEventListener()

5. Un piccolo progetto

Per esempio:

  • contatore;
  • lista di attività;
  • form con validazione;
  • filtro di una lista;
  • quiz;
  • chiamata a una semplice API.

Un piccolo progetto completo ti costringe a collegare variabili, funzioni, eventi e DOM molto più di venti esercizi isolati.

Dopo questi passaggi puoi affrontare richieste HTTP e approfondire il modello asincrono. Se incontri codice storico o progetti WordPress esistenti, può essere utile capire anche cos’è jQuery e perché è stato così utilizzato: la libreria ha avuto un ruolo centrale nel semplificare operazioni DOM e AJAX, anche se molte operazioni che in passato la richiedevano oggi possono essere svolte direttamente con le API native del browser.

Quando ha senso passare a TypeScript, librerie o framework

Una delle scorciatoie meno utili per chi inizia è imparare immediatamente un framework senza avere un modello mentale di JavaScript.

Puoi riuscire a copiare componenti e farli funzionare, ma diventa difficile capire:

  • perché uno stato cambia;
  • cosa produce un errore;
  • cosa è asincrono;
  • quali API appartengono al browser;
  • cosa fa parte del linguaggio e cosa appartiene al framework.

Prima di passare a strumenti più complessi conviene avere una buona familiarità con:

  • funzioni;
  • array e oggetti;
  • moduli;
  • DOM;
  • eventi;
  • Promise;
  • async/await.

TypeScript diventa particolarmente interessante quando vuoi aggiungere un sistema di tipi statici e controlli in fase di sviluppo a una codebase esistente.

Un framework può avere senso quando devi gestire interfacce e stato di maggiore complessità o quando il progetto e il team ne traggono un vantaggio concreto.

Non esiste però un momento in cui “devi” abbandonare JavaScript puro. Per molte interazioni del web, le API native restano sufficienti.

Conclusione

Capire JavaScript diventa molto più semplice quando smetti di considerarlo come un blocco unico che “fa funzionare il web”.

Il modello più utile è questo: ECMAScript definisce il linguaggio; un motore lo esegue; il runtime aggiunge le funzionalità dell’ambiente; nel browser le Web API permettono al codice di interagire con pagina, rete ed eventi.

Da qui discende quasi tutto il resto.

Se vuoi impararlo, non partire da framework, librerie o decine di metodi da memorizzare. Parti da variabili, funzioni, condizioni e dati; passa poi a DOM ed eventi; quindi affronta asincronia e moduli. Quando queste fondamenta sono chiare, anche gli strumenti più avanzati smettono di sembrare una collezione di formule da copiare e diventano semplicemente un altro livello costruito sopra il linguaggio.