Frontend

Valori, Primitive, Variabili e Scope in JavaScript

Autore

Manuel Ricci

In questa lezione, ci immergeremo nel cuore di JavaScript, esplorando i suoi concetti fondamentali che ogni sviluppatore, sia novizio che esperto, dovrebbe padroneggiare.

Inizieremo col comprendere cosa sono i valori in JavaScript e come vengono manipolati attraverso le variabili. Questo ci porterà a discutere le sette tipologie di valori primitivi che formano il tessuto di qualsiasi logica in JavaScript. Successivamente, ci addentreremo nel modo in cui JavaScript permette di dichiarare le variabili attraverso var, let e const, ognuno con le sue peculiarità e casi d'uso.

La comprensione dello scope, sia a livello di blocco, locale e globale, è fondamentale per scrivere codice chiaro e privo di errori, e quindi dedicheremo una parte significativa dell'articolo a questo argomento.

Infine, esploreremo un concetto fondamentale ma spesso frainteso: il modo in cui le variabili in JavaScript si collegano ai valori, piuttosto che contenerli, una distinzione che ha importanti implicazioni nella programmazione di JavaScript, in particolare quando si lavora con tipi non primitivi come gli oggetti.

Attraverso questo approfondimento, cerco di fornire una comprensione profonda e intuitiva di JavaScript, permettendoti di sfruttare appieno le sue potenzialità e di navigare con sicurezza nel suo ecosistema in continua evoluzione.

Valori in JavaScript

I valori in JavaScript sono sostanzialmente due:

  1. Oggetti: collezione di proprietà. Una proprietà è un'associazione chiave/valore. Il valore di una proprietà può essere una funzione, in quel caso la proprietà prende il nome di metodo.
  2. Primitive: valori che non sono oggetti (non hanno proprietà o metodi)

Le 7 primitive di JavaScript

Le primitive in JavaScript sono i blocchi fondamentali del linguaggio, che rappresentano i tipi di dati più semplici e immutabili. Ogni primitiva ha caratteristiche e usi specifici che ne definiscono l'importanza nel mondo della programmazione JavaScript.

Qui di seguito riassumo brevemente, ma se sei interessato ad un maggior approfondimento ho già parlato delle primitive in passato e puoi leggere l’approfondimento sulle primitive in JavaScript.

1. Number

Il tipo Number in JavaScript è abbastanza flessibile, rappresentando sia valori interi che a virgola mobile. L'adozione dello standard IEEE 754 per la rappresentazione a 64 bit garantisce una precisione elevata nei calcoli, ma introduce alcune peculiarità, come la rappresentazione di valori speciali come Infinity e NaN (Not a Number). Questa flessibilità fa di Number un tipo di dato versatile, usato in una vasta gamma di calcoli matematici e operazioni finanziarie.

2. String

Le stringhe in JavaScript sono immutabili, il che significa che una volta creata una stringa, i suoi caratteri non possono essere modificati. La codifica UTF-16 permette la rappresentazione di una vasta gamma di caratteri, rendendo JavaScript un linguaggio adatto per lo sviluppo di applicazioni globali. Le stringhe vengono utilizzate in quasi tutti gli aspetti della programmazione JavaScript, dalla manipolazione del testo alla comunicazione dei dati.

3. Boolean

Il tipo Boolean rappresenta la logica binaria e può avere solo due valori: true o false. Questo tipo è fondamentale per il controllo di flusso in JavaScript, come nelle condizioni if e nei cicli while. È anche ampiamente utilizzato in confronti e operazioni logiche, rendendolo un elemento essenziale nella presa di decisioni all'interno del codice.

4. Undefined

Undefined è un tipo speciale in JavaScript, che rappresenta l'assenza di un valore assegnato. Una variabile dichiarata ma non inizializzata avrà automaticamente il valore undefined. Questo tipo è spesso utilizzato per verificare se una variabile è stata impostata o meno, ed è un concetto fondamentale per capire come JavaScript assegna e gestisce i valori.

5. Null

Simile a undefined, null rappresenta un'intenzionale assenza di valore, ma deve essere assegnato esplicitamente. È comunemente utilizzato in situazioni dove si desidera indicare che una variabile dovrebbe contenere un oggetto, ma attualmente non ne contiene nessuno. La distinzione tra null e undefined è sottile ma importante per scrivere codice chiaro e intenzionale.

6. Symbol

Introdotti con ECMAScript 2015 (ES6), i Symbol offrono un modo per creare identificatori unici, che sono utili per definire proprietà uniche di oggetti, evitando conflitti di nome. A differenza delle altre primitive, ogni Symbol è unico, anche se creato con lo stesso valore. Questo li rende ideali per l'uso come chiavi di proprietà in oggetti, dove è richiesta unicità e privacy.

7. BigInt

BigInt è una recente aggiunta a JavaScript, che permette la rappresentazione di numeri interi di dimensioni arbitrarie. Questo supera i limiti del tipo Number per grandi valori interi, consentendo calcoli di precisione su numeri molto grandi, che sono particolarmente utili in contesti come la crittografia o la manipolazione di grandi dataset.

Dichiarare le Variabili

In JavaScript abbiamo tre parole riservate per dichiarare una variabile:

  • var: Le variabili dichiarate con var sono soggette a "hoisting", il che significa che sono elevate all'inizio del loro scope funzionale.
  • let: Risolve alcuni dei problemi di var, come l’hoisting e le restrizioni di scope. let permette di dichiarare variabili che sono limitate nello scope di un blocco.
  • const: Simile a let in termini di scope di blocco, ma con const, una volta che una variabile è assegnata, il suo valore non può essere cambiato (è importante notare che questo non rende l'oggetto immutabile, solo la sua assegnazione).

Mentre var è soggetta a scope globale e locale, let e const sono soggette a scope di blocco, ma cosa significa esattamente?

Scope delle variabili in JavaScript

L'ambito dello scope in JavaScript è un concetto cruciale che definisce la visibilità e la disponibilità delle variabili e delle funzioni in diverse parti del codice. Vediamo nel dettaglio i tre scope di JavaScript.

Tra l’altro dello scope in JavaScript ne ho già parlato nell’approfondimento dedicato e ci ho fatto anche un video.

Scope Globale

Quando parliamo di scope globale in JavaScript, ci riferiamo all'ambiente più esterno. Le variabili o le funzioni dichiarate in questo ambito sono accessibili da qualsiasi altra parte del codice, indipendentemente da dove si trovano. Questo livello di accessibilità rende lo scope globale potente, ma anche potenzialmente pericoloso. Variabili globali possono essere sovrascritte o modificate da qualsiasi parte del codice, aumentando il rischio di errori e conflitti, specialmente in applicazioni grandi o complesse.

Per esempio, se dichiariamo una variabile a livello globale:

1var messaggio = "Ciao, mondo!";

Questa variabile può essere acceduta e modificata da qualsiasi funzione o blocco di codice nel nostro programma.

Scope Locale

Al contrario, lo scope locale si riferisce a variabili o funzioni che sono accessibili solo all'interno della funzione in cui sono state dichiarate. Questo isola le variabili dal resto del codice, proteggendole da interazioni involontarie e rendendo il codice più facile da comprendere e mantenere. In JavaScript, ogni funzione crea un nuovo scope locale.

Ad esempio:

1function saluta() {
2    var saluto = "Ciao!";
3    console.log(saluto); // Output: "Ciao!"
4}
5saluta();
6console.log(saluto); // Errore: saluto non è definito

Qui, saluto è una variabile locale alla funzione saluta e non è accessibile al di fuori di essa.

Scope di Blocco

JavaScript ES6 ha introdotto let e const, che permettono la dichiarazione di variabili con scope di blocco. Questo significa che queste variabili sono limitate al blocco, istruzione o espressione in cui sono state dichiarate. Ciò è particolarmente utile nei cicli e nelle istruzioni condizionali, dove si desidera che una variabile sia disponibile solo all'interno di un certo contesto.

Ad esempio:

1if (true) {
2    let esempio = "Ciao!";
3    console.log(esempio); // Output: "Ciao!"
4}
5console.log(esempio); // Errore: esempio non è definito

In questo caso, esempio è limitata all'interno del blocco if e non è accessibile al di fuori.

Le variabili contengono dei valori? No…

La frase "Le variabili contengono dei valori" è una semplificazione che, sebbene intuitiva, non cattura accuratamente la natura delle variabili in molti linguaggi di programmazione, inclusi JavaScript. In realtà, una variabile è meglio compresa come un riferimento o un collegamento a un valore memorizzato in un'area della memoria del computer. Questa distinzione è fondamentale per comprendere come JavaScript gestisce i dati, in particolare quando si lavora con tipi più complessi come gli oggetti.

JavaScript Engine, Call Stack e Heap

Per comprendere come le variabili sono gestite in JavaScript, è importante avere un'idea di come funziona il JavaScript engine. Questo motore è il cuore di ogni ambiente di esecuzione JavaScript, come i browser o Node.js. È responsabile dell'esecuzione del codice, della gestione della memoria e dell'elaborazione delle istruzioni.

  1. JavaScript Engine: Si compone di diversi componenti, tra cui il motore di esecuzione, il compilatore Just-In-Time (JIT), il call stack e l'heap.

  2. Call Stack: Il call stack è una struttura di dati che tiene traccia delle funzioni che sono in esecuzione. Quando una funzione viene chiamata, viene aggiunta (o "pushed") sul call stack. Quando la funzione termina, viene rimossa (o "popped") dal call stack. Questo è importante per tenere traccia dell'esecuzione del programma, ma anche per capire come vengono gestite le variabili locali.

  3. Heap: L'heap è una regione di memoria non strutturata dove gli oggetti sono memorizzati. A differenza del call stack, che ha una natura LIFO (Last In, First Out) e una dimensione limitata, l'heap ha una struttura più libera e può espandersi per accomodare un gran numero di oggetti.

Variabili e Memoria in JavaScript

Quando scriviamo codice JavaScript, le variabili che utilizziamo sono gestite in modi diversi a seconda che si tratti di tipi primitivi o oggetti:

  • Tipi Primitivi: Quando assegniamo un valore primitivo a una variabile, questo valore viene spesso memorizzato direttamente nel call stack. Questo perché i valori primitivi sono immutabili e di dimensioni relativamente piccole, il che li rende adatti per lo storage efficiente nello stack.

  • Oggetti: Al contrario, quando creiamo un oggetto, questo viene memorizzato nell'heap, che è più adatto per contenere strutture di dati di dimensioni maggiori e di natura dinamica. La variabile a cui è assegnato l'oggetto non contiene l'oggetto stesso ma un riferimento, ovvero un indirizzo dell'heap dove l'oggetto è memorizzato. Questo riferimento è quello che viene effettivamente memorizzato nel call stack.

Implicazioni Pratiche

Questa distinzione ha implicazioni importanti per la programmazione in JavaScript:

  • Gestione della Memoria: Capire dove e come i dati sono memorizzati aiuta a scrivere programmi più efficienti e a evitare problemi di gestione della memoria, come perdite di memoria.

  • Passaggio di Valori: Quando passiamo una variabile a una funzione, se è un tipo primitivo, viene passata una copia del valore. Se è un oggetto, viene passato il riferimento. Questo influisce su come le funzioni possono modificare i dati passati.

  • Performance: Operazioni su variabili che risiedono nel call stack tendono ad essere più veloci rispetto a quelle sull'heap a causa della gestione della memoria e delle caratteristiche del garbage collector.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.