Appunti di JavaScript Modern

Share:

A causa della mia rinomata pigrizia, ho deciso di apprendere JavaScript Modern con un certo ritardo. Quando sentii parlare per la prima volta delle novità introdotte dalla versione 6 nell’ormai lontano 2015,  pensai che si trattasse della solita tecnologia fuffa e infatti per qualche annetto me ne infischia allegramente, tuttavia mi sbagliavo. A prescindere dai soliti slogan riportati sui siti tematici acchiappaclic (la nostra vita cambierà, diventeremo tutti Bill Gates, ecc…) parliamo di qualcosa che ha effettivamente avuto un impatto forte nel settore dello sviluppo basato su framework JavaScript. Da quel momento, le varie versioni si sono susseguite fino alla versione 15 rilasciata nel 2024 ed oggi, assieme a TypeScript è lo standard per usare tutta una serie di strumenti fondamentali. Quindi a prescindere dalle chiacchiere, è utile sul serio. Nel corso del tempo ho creato una sorta di quaderno ad anelli per JavaScript Modern e un vero e proprio manuale ad uso personale su TypeScript (che pure mi riservo di condividere qui più avanti). Scrivere questo articolo è un modo per ripassare alcuni concetti e mettere in ordine i miei appunti.

Le variabili

Una delle prime cose che ho notato studiando il nuovo JavaScript è il cambiamento nella dichiarazione delle variabili. Mentre in JavaScript classico usavamo var, nella versione moderna vengono introdotti let e const per una gestione più blindata. In altre parole non avendo una tipizzazione forte come altri linguaggi, si usa queste direttive per impedire di pasticciare con i dati. La solita var ha un comportamento flessibile che può portare a comportamenti inaspettati, invece let e const non soffrono di questo problema. Con let possiamo dichiarare variabili riassegnabili e con const costanti che non possono essere riassegnate.
Inizialmente ho avuto una certa confusione iniziale, anche perché vedevo i codici JS proposti a volte con le vecchie definizioni. Questo perché, JavaScript è come il maiale: non si butta via niente: i browser accettano var, i compilatori accettano var, tutti accettano var, però ovunque si sconsiglia l’uso di var. Come il  maiale, appunto: si può mangiare tutto ma alcune parti sono sconsigliate.
Da quello che ho capito, ufficialmente non è un errore sintattico grave in sé, può solo portare a problemi di leggibilità del codice. Dal punto di vista pratico, se vogliamo studiare o sgraffignare snippet da usare per il nostro lavoro, vedremo sempre meno spesso var. Quindi meglio abituarsi. Facciamo un esempio al volo (a questo proposito io mi trovo benissimo a testare e studiare i codici su playcode).

let pippo = 5;
pippo = 27; // si può fare
console.log(pippo); // pippo

const pluto = 30;
//pluto = 340; // non si può fare
console.log(pluto);// errore

Altra sintassi che inizialmente vedevo come il fumo negli occhi riguarda le arrow functions. Cosa sono le arrow funcions? Io le chiamo impropriamente funzioni scorciatoia. Un modo per scrivere in modo veloce istruzioni che devono restituire un valore.

 // funzione classica
function somma(a, b) {
return a + b;
}
console.log(somma(2,6));

// funzione scorciatoia
const somma = (a, b) => a + b;
console.log(somma(2,6));

La sintassi si adatta anche per le funzioni in linea. Se dobbiamo scrivere una funzione dichiarata all’interno di un metodo che lo prevede, possiamo fare a meno del codice function(e). Facciamo un esempio con listener:

const bottone = document.createElement(‘button’);
bottone.textContent = ‘Cliccami’;
document.body.appendChild(bottone);
// Aggiunge un event listener per il click
bottone.addEventListener(‘click’, () => {
console.log(‘Ciao Mondo!’); // Ciao Mondo!
});

Per le funzioni freccia vale lo stesso discorso fatto in precedenza a proposito di var. Si tratta di una sintassi che anche se non amiamo scrivere, dobbiamo abituarci a leggere.

Oggetto globale universale

A partire dal 2020 è stato introdotto l’oggetto globale universale globalThis. Questo oggetto serve a fornire un modo standard per accedere all’oggetto globale, indipendentemente dall’ambiente JavaScript in cui ci si trova. Prima di globalThis, diversi ambienti avevano nomi diversi per l’oggetto globale (window nei browser, global in Node.js e così via). Si tratta di una trovata niente male che ci consente non dover usare sintassi diverse per ottenere lo stesso scopo. La sintassi è semplicissima:

globalThis.varGlobale = “Ciao Mondo!”;
console.log(globalThis.varGlobale);
// Output: “Ciao Mondo!”

console.log(globalThis.varGlobale === window.varGlobale);
// Output: true (in un browser)

console.log(globalThis.varGlobale === global.varGlobale);
// Output: true (in Node.js)

Nelle prime due righe viene creata una variabile globale denominata varGlobale grazie all’oggetto globaThis. L’accesso a questa variabile sarà possibile a prescindere dall’ambiente in cui operiamo. Le righe successive verificano dove viene eseguito il codice. In un ambiente browser, globalThis è equivalente a window mentre in Node.js, globalThis è equivalente a global.

Gli operatori logici combinati e di Accorpamento Nullish

Un’altra funzionalità per programmatori pigri (come me) sono i Logical Assignment Operators, operatori che consentono di combinare operazioni logiche con l’assegnazione in un’unica operazione, rendendo il codice più conciso. Gli operatori  sono 3:

  • Logical AND assignment (&&=) assegna il valore alla variabile solo se la variabile è già valutata come vera (truthy).
  • Logical OR assignment (||=): assegna il valore alla variabile solo se la variabile è valutata come falsa (falsy).
  • Nullish coalescing assignment (??=): assegna il valore alla variabile solo se la variabile è null o undefined.

let hero = {
   name: ‘Spiderman’,
   alias: ”,
   powers: null,
   city: ‘New York’
};
hero.alias &&= ‘Friendly Neighborhood Spiderman’;//no
console.log(hero.alias); //no
hero.powers ||= [‘arrampicarsi’, ‘superforza’, ‘senso di ragno’]; //sì
console.log(hero.powers);
// [ ‘arrampicarsi’, ‘superforza’, ‘senso di ragno’ ]

hero.city ??= ‘Queens’; //no
console.log(hero.city); // New York
hero.realName ??= ‘Peter Parker’; //sì
console.log(hero.realName);// Peter Parker

Nell’esempio l’assegnazione avviene solo quando la condizione è soddisfatta e di conseguenza il messaggio di output restituirà una stringa solo in quel caso. Nell’esempio ho commentato le condizioni soddisfatte con “sì” e quelle non soddisfatte con “no”.
Volendo semplificare il discorso, potremmo voler fornire un valore di default solo quando una variabile è non è definita. In casi come questi è molto comodo l’operatore di Accorpamento Nullish che si indica con il doppio punto interrigativo (??). La sintassi di base è semplicissima:

let result = value1 ?? value2;  

In questo caso restituisce l’operando di destra se l’operando di sinistra è null o undefined altrimenti, restituisce l’operando di sinistra. Facciamo un esempio più complesso. Immaginiamo di avere delle informazioni di partenza e di voler fornire valori di default per eventuali informazioni mancanti:

let nomeEroe = null;
let aliasEroe = undefined;
let cittàEroe = ‘Gotham’;
// Usando l’operatore di accorpamento Nullish (??)
let nome = nomeEroe ?? ‘Nome Predefinito’;
let alias = aliasEroe ?? ‘Alias Sconosciuto’;
let città = cittàEroe ?? ‘Città Predefinita’;
console.log(nome);
// Output: Nome Predefinito (poiché nomeEroe è null)

console.log(alias);
// Output: Alias Sconosciuto (poiché aliasEroe è undefined)

console.log(città);
// Output: Gotham (poiché cittàEroe non è null né undefined)

L’operatore di accorpamento Nullish ci assicura che i valori predefiniti vengano utilizzati solo quando le variabili sono effettivamente null o undefined, evitando comportamenti indesiderati con valori come il numero zero oppure stringhe vuote.

Manipolare le stringhe

Con JavaScript Modern, non dobbiamo necessariamente concatenare stringhe usando il vecchio operatore +. Possiamo usare anche i template literals, che rendono il codice pieno di dollari (ma che non ci renderà più ricchi).

let nome = “Pippo”;
let messaggio = `Ciao ${nome}!`;
console.log(messaggio);
// Restitusce: Ciao Pippo!

Occhio a non confondere le backtick (`) con le virgolette singole (come feci io all’epoca insultando il povero JS). In pratica si usa l’accento inverso per racchiudere gli elementi che vorremmo concatenare. Da tastiera Windows si ottiene anche con ALT+96.
Un’altra funzione interessante per manipolare in modo veloce le stringhe sono padStart e padEnd. Si tratta di due scorciatoie che ci consentono di aggiunge caratteri all’inizio o alla fine di una stringa specificando una certa lunghezza length specificata. Facciamo un esempio con padStart():

let numero = “2”;
// Aggiunge 3 zeri all’inizio
let prefisso = numero.padStart(4, ‘0’);
console.log(prefisso);
// Output: “0002”

In questo codice diciamo di aggiungere tanti zeri fino a quando la stringa risultante non sia composta da length pari a 4. Siccome in partenza è di 1, vengono aggiunti i tre “0”. Sembra una sciocchezza, ma ci sono modi più complicati per ottenere lo stesso risultato. Discorso analogo per il metodo padEnd():

let numero = “2”;
// Aggiunge 3 zeri alla fine
let prefisso = numero.padEnd(4, ‘0’);
console.log(prefisso);
// Output: “2000”

In questo caso avremo i tre zeri aggiunti alla fine. Queste funzionalità sono particolarmente utili per formattare dei dati stringa in progetti specifici. Un’altra funzione interessante è replaceAll(). Questa funzione permette di sostituire tutte le occorrenze di una stringa all’interno di un’altra con una nuova stringa. Sembra uno scioglilingua ma è molto semplice. La sintassi generale è: string.replaceAll(cercaVal, sostituisciVal), dove cercaVal indica la stringa o espressione regolare da cercare e sostituisci la stringa che sostituirà ogni occorrenza. Facciamo un esempio al volo. Supponiamo di avere una lista di personaggi sotto forma di stringa e di voler sostituire un supereroe con un altro.

let supereroi = “Batman, Superman, Spiderman, Superman, Ironman”;
// Sostituiamo tutte le occorrenze di “Superman” con “Wonder Woman”
let nuoviSupereroi = supereroi.replaceAll(“Superman”, “Wonder Woman”);
console.log(nuoviSupereroi);
// Output: “Batman, Wonder Woman, Spiderman, Wonder Woman, Ironman”

In questo esempio In questo esempio, tutte le occorrenze di “Superman” sono state sostituite con “Wonder Woman”. La funzione replaceAll() supporta anche le espressioni regolari. Ad esempio, se volessimo sostituire tutte le parole “Marvel” con “DC”, potemmo usare questa espressione regolare.

let stringa = “Marvelcomics, Marvelsaga, Marveluniverse”;
// Sostituiamo tutte le occorrenze di “Marvel” con “DC” utilizzando un’espressione regolare
let nuovaStringa = stringa.replaceAll(/Marvel/g, “DC”);
console.log(nuovaStringa);
// “DCcomics, DCsaga, DCuniverse”

In questo caso l’espressione regolare /Marvel/g viene passato come parametro di ricerca a replaceAll() che cerca tutte le occorrenze della parola “Marvel” nella stringa.
Se invece ci vogliamo limitare ad eliminare gli spazi che ci potrebbero essere all’inizio o alla fine di una stringa, possiamo usare i metodi trimStart() e trimEnd(). Il primo cancella lo spazio vuoto all’inizio di una stringa e il secondo lo spazio vuoto alla fine di una stringa. Facciamo un esempio con una frase che ha tre spazi vuoti all’inizio e alla fine:

let urloAvengers = ‘   Avengers Assemble!   ‘;
console.log(‘Originale:’,`”${urloAvengers}”`);
//Originale: ”   Avengers Assemble!   “;
let pulisciInizio = urloAvengers.trimStart();
console.log(‘metodo trimStart:’,`”${pulisciInizio}”`);
//metodo trimStart: “Avengers Assemble!   “;
let pulisciFine = urloAvengers.trimEnd();
console.log(‘metodo trimEnd:’,`”${pulisciFine}”`);
//metodo trimEnd: ”   Avengers Assemble!”;
let pulisciTutto = urloAvengers.trimStart().trimEnd();
console.log(‘trimStart e trimEnd:’,`”${pulisciTutto}”`);
//Entrambi i metodi: “Avengers Assemble!”;

In questo caso i metodi sono stati usati prima separatamente e poi assieme.

Gestire gli oggetti con entries() e values()

I metodi entries() e values() sono due metodi pensati per manipolare o analizzare dati contenuti negli oggetti JavaScript. Il primo ci consente convertire un oggetto in un array di coppie chiave-valore e quindi gestire facilmente i dati. Ad esempio, è possibile filtrare o mappare le coppie chiave-valore. Il secondo ci consente di trasformare i valori delle proprietà in un array e quindi poter approfittare delle caratteristiche degli array. Per esempio potremmo controllare la presenza di un determinato valore. Facciamo un esempio con entries().

const superheroes = {
superman: “Clark Kent”,
batman: “Bruce Wayne”,
wonderWoman: “Diana Prince”,
};
// crea un array di coppie [chiave, valore]
const entries = Object.entries(superheroes);
console.log(entries);
// Restituisce:
// [
// [‘superman’, ‘Clark Kent’],
// [‘batman’, ‘Bruce Wayne’],
// [‘wonderWoman’, ‘Diana Prince’]
// ]
// legge le coppie con forEach
entries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// Restituisce:
// superman: Clark Kent
// batman: Bruce Wayne
// wonderWoman: Diana Prince

Nella prima parte del codice convertiamo l’oggetto in un Array di coppie e nella seconda usiamo un ciclo forEach per leggere i valori del nostro Array. Adesso facciamo un esempio con values():

const superheroes = {
superman: “Clark Kent”,
batman: “Bruce Wayne”,
wonderWoman: “Diana Prince”,
};
const values = Object.values(superheroes);
console.log(values);
// Output:
// [‘Clark Kent’, ‘Bruce Wayne’, ‘Diana Prince’]
values.forEach(value => {
console.log(value);
});
// Output:
// Clark Kent
// Bruce Wayne
// Diana Prince

In questo caso creiamo un array contenente tutti i valori di un oggetto, senza le chiavi. Successivamente usiamo forEach per iterare gli elementi dell’array costituito appunto da valori.

Le classi in JavaScript

Anche la versione moderna di JavaScript ha le classi. Le classi in JavaScript, come quelle di altri linguaggi più blasonati, sono una sintassi per creare oggetti e gestire l’ereditarietà. Usare questo strumento implica una sintassi più chiara e concisa per creare oggetti rispetto ai costruttori tradizionali basati su funzioni. Per dichiarare una classe, si utilizza la solita parola chiave class seguita dal nome della classe. Come negli altri linguaggi di programmazione, all’interno della classe, si definiscono un costruttore e i metodi.

class Supereroi {
   constructor(nome, poteri) {
   this.nome = nome;
   this.poteri = poteri;
   }
   messaggio() {
   return `Sono ${this.nome} e posso ${this.poteri}.`;
  }
}

In questo esempio, Supereroi è una classe con un costruttore che inizializza le proprietà nome e poteri. Il metodo messaggio() restituisce una frase sulle caratteristiche del supereroe. Possiamo anche creare un’istanza della classe utilizzando la parola chiave new.

const incredibileHulk = new Supereroi(‘Bruce Banner’, ‘diventare Hulk’);
console.log(incredibileHulk.messaggio());
//Sono Bruce Banner e posso diventare Hulk.

Anche le classi JavaScript prevedono l’ereditarietà. Possiamo creare una nuova classe basata su una classe esistente. La nuova classe (sottoclasse) eredita tutte le proprietà e i metodi della classe esistente (superclasse). Come per i linguaggi classici, per creare una sottoclasse si utilizza la parola chiave extends seguita dal nome della superclasse. La sottoclasse può avere il proprio costruttore e metodi aggiuntivi, e può anche sovrascrivere i metodi della superclasse.

class eroeInTeam extends Supereroi{
   constructor(nome, poteri, gruppo) {
   super(nome, poteri);
   this.gruppo = gruppo;
   }
   descrizione() {
   return `${this.messaggio()} Milito nei ${this.gruppo}.`;
   }
}

In questo esempio, eroeInTeam è una sottoclasse di Supereroi. Utilizza super per chiamare il costruttore della superclasse e inizializzare nome e poteri. Aggiunge anche una nuova proprietà gruppo e un metodo denominato descrizione (nota per gli appassionati di fumetti, Hulk a differenza di quello che si vede al cinema, non milita in nessun gruppo). Possiamo creare un’istanza della sottoclasse nello stesso modo in cui si crea un’istanza della superclasse.

const torciaUmana = new eroeInTeam(‘Jhonny Storm’, “Infiammarmi”, ‘Fantastici Quattro’);
console.log(torciaUmana.descrizione());
//Sono Jhonny Storm e posso Infiammarmi. Milito nei Fantastici Quattro.

I metodi statici sono definiti utilizzando la parola chiave static. Possono essere chiamati direttamente sulla classe, senza creare un’istanza. Facciamo un esempio creando una nuove versione della classe Supereroi:

class Supereroi {
   static teamup(eroe1, eroe2) {
   return `${eroe1} e ${eroe2} uniscono le loro forze!`;
   }
}
console.log(Supereroi.teamup(‘Superman’, ‘Batman’));
//Superman e Batman uniscono le loro forze!

In questo esempio, il metodo teamup() è un metodo statico della classe Supereroi. Può essere chiamato direttamente sulla classe senza creare un’istanza.
Vediamo adesso come funziona l’incapsulamento in JavaScript. Con l’introduzione delle proprietà e metodi privati, è possibile creare membri della classe che non sono accessibili dall’esterno. JavaScript supporta anche membri privati utilizzando il prefisso (#). Le proprietà private non sono accessibili al di fuori della classe in cui sono dichiarate. Modifichiamo la classe come segue:

class Supereroi {
   #identitaSegreta = ”;
   constructor(nome, identitaSegreta) {
   this.nome = nome;
   this.#identitaSegreta = identitaSegreta;
   }
   rivelaIdentita() {
   return `L’identità segreta di ${this.nome} è ${this.#identitaSegreta}.`;
   }
}
const supereroe = new Supereroi(‘Daredevil’, ‘Matt Murdock’);
console.log(supereroe.nome);
// Output: Daredevil

console.log(supereroe.rivelaIdentita());
// Output: L’identità segreta di Daredevil è Matt Murdock.
console.log(supereroe.#identitaSegreta); // Errore

In questo esempio, #identitaSegreta è una proprietà privata della classe Supereroe. Può essere letta dal metodo rivelaidentita() perché si trova all’interno della classe, ma la stessa proprietà non può essere accessibile al di fuori della classe. Infatti quando proviamo a leggerla direttamente al di fuori, restituisce un errore all’ultima riga. Per poter far funzionare il codice nel simulatore sarà necessario commentare o cancellare proprio la riga finale.

Destructuring

Il destructuring è una funzionalità che permette di estrarre valori da array o proprietà da oggetti in modo molto più veloce e conciso. Questa sintassi semplifica la gestione dei dati, migliorando la leggibilità, Il destructuring è particolarmente utile in vari contesti, tra cui la possibilità di passare rapidamente valori di un oggetto come argomenti di una funzione e la manipolazione del DOM per estrarre attributi da elementi. Quando si lavora con oggetti, possiamo estrarre e assegnare valori alle variabili in una sola riga di codice.

const libro = {
titolo: “Io Robot”,
pagine: 252,
autore: “Isaac Asimov”
};

// Senza destructuring
const titolo = libro.titolo;
const pagine = libro.pagine;
const autore = libro.autore;

// Con destructuring
const { titolo, pagine, autore } = libro;
console.log(titolo); // Io Robot
console.log(pagine); // 252
console.log(autore); // Isaac Asimov

Questo meccanismo ci fornisce la possibilità di usare una sintassi di destrutturazione per identificare e controllare non solo elementi specifici ma anche tutti quelli che restano.

const numeri = [1, 2, 3, 4, 5];
// Senza destructuring
const primo = numeri[0];
const secondo = numeri[1];

// Con destructuring
const [primo, secondo, …resto] = numeri;
console.log(primo); // 1
console.log(secondo); // 2
console.log(resto); // [3, 4, 5]

Il destructuring può essere utilizzato anche nei parametri delle funzioni, rendendo il passaggio di oggetti più intuitivo.

const regolaForno = {
timer: 30,
temperatura: 220,
modalità: “ventilato”
};

// Senza destructuring
function applicaImpostazioni(regolaForno) {
const timer = regolaForno.timer;
const temperatura = regolaForno.temperatura;
const modalità = regolaForno.modalità;
console.log(timer, temperatura, modalità);
}
applicaImpostazioni(regolaForno);
// 30 220 ventilato

const regolaForno = {
timer: 30,
temperatura: 220,
modalità: “ventilato”
};

// Con destructuring
function applicaImpostazioni({ timer, temperatura, modalità }) {
console.log(timer, temperatura, modalità);
 }
applicaImpostazioni(regolaForno);
// 30 220 ventilato

Possiamo anche assegnare valori predefiniti durante il destructuring, utile quando alcune proprietà o elementi potrebbero essere non definiti. Effettivamente il destructuring è uno strumento versatile che può semplificare il codice facendo risparmiare tempo. Bisogna farci la mano, ma conviene usarlo.

Usare gli Array in JavaScript Modern 

Di seguito riporto un breve elenco con gli strumenti utili a gestire gli Array in JavaScript che sono stati aggiunti a partire dalla versione ES6 in poi. Il primo strumento di questo mini elenco è includes. Si tratta di un metodo per stabilire se un array contiene un determinato elemento, restituendo true o false a seconda del caso.  La sintassi è semplicissima:

const romanzi= [“Dune”, “Neuromante”, “Fondazione”, “Hyperion”];
console.log(romanzi.includes(“Dune”)); // true
console.log(romanzi.includes(“La Zona Morta”)); // false

Semplice, ma estremamente comodo. Passiamo ad un altro strumento molto intuitivo, parliamo dell’operatore Spread (niente a che vedere con quella robaccia legata alla finanza). Questo metodo espande un array o un oggetto in singoli elementi o proprietà. Facciamo il solito esempio al volo:

const autoriSF = [“Asimov”, “Clarke”];
const autoriFantasy = [“Tolkien”, “Brooks”];
const autoriTutti = […autoriSF, …autoriFantasy];
console.log(autoriTutti);
// [‘Asimov’,’Clarke’,’Tolkien’,’Brooks’]

In questo esempio, l’operatore Spread espande gli elementi di authors1 e authors2 all’interno di un nuovo array autoriTutti. Vediamo adesso altre un’altra funzionalità interessante che più o meno fa qualcosa di simile. Mi riferisco al metodo flat() attraverso il quale possiamo prendere un array caratterizzato da elementi nidificati per poi convertirlo in un array semplice privo di nidificazione (nel gergo si dice appiattito).

const lbNidificati = [“Dune”,[“Io Robot”,”Fondazione”],[“Neuromante”,[“Snow Crash”]]];
const lbriFlat = lbNidificati.flat(2);
console.log(lbriFlat);
// [‘Dune’,’Io Robot’,’Fondazione’,’Neuromante’,’Snow Crash’]

In questo caso, abbiamo usato il metodo flat per appiattire l’array di partenza. Il numero inserito tra parentesi è il parametro depth e serve a specificare la profondità con la quale vogliamo appiattire l’array. Con valore 2 vuol dire che dovremo considerare anche elementi come [“Neuromante”, [“Snow Crash”]]. Il risultato sarà un array completamente appiattito senza ulteriori livelli di annidamento. Una variante di flat è il metodo flatMap(). Come si intuisce leggendo il nome, questo metodo è equivalente a una combinazione di flat() e map().  Oltre a restituire un array appiattito, possiamo eseguire una funzione di mappatura su ciascun elemento dell’array. Facciamo un esempio:

const romanzi = [
{ titolo: “Dune”, autore: “Frank Herbert” },
{ titolo: “Neuromante”, autore: “William Gibson” },
{ titolo: “Fondazione”, autore: “Isaac Asimov” }
];
const per_titoli = romanzi.flatMap(book => book.titolo);
console.log(per_titoli);
// [“Dune”, “Neuromante, Fondazione”]

Il parametro di flatMap è una funzione che produce un nuovo valore per ogni elemento dell’array. In questo caso otterremo un nuovo array composto dai titoli dei tre romanzi. Concludiamo questa carrellata sugli array con altre due scorciatoie molto comode: findLast() e findLastIndex(). Si tratta di due metodi permettono di trovare un elemento che soddisfa una certa condizione. Vediamo come funziona il primo dei due.

const romanzi= [“Hyperion”, “Neuromante”, “Fondazione”, “Dune”,”Snow Crash”,];
const lastDune = romanzi.findLast(libro => libro === “Dune”);
console.log(lastDune); // “Dune”

Il metodo findLast() restituisce l’ultimo elemento di un array che soddisfa una determinata condizione. La ricerca viene effettuata da destra verso sinistra (dall’ultimo elemento al primo). In questo caso la condizione è un titolo uguale a Dune. Se non avesse trovato nulla avremmo ottenuto undefined. Il metodo  findLastIndex() funziona in modo molto simile a findLast(), con la differenza che invece di restituire l’elemento dell’array che soddisfa la condizione, restituisce l’indice (la posizione) dell’elemento che soddisfa la condizione.

const romanzi= [“Hyperion”, “Neuromante”, “Fondazione”, “Dune”,”Snow Crash”,];
const lastDuneIndex = romanzi.findLastIndex(libro => libro === “Dune”);
console.log(lastDuneIndex); // 3

In questo caso restituisce l’indice 3, che è la posizione dell’ultimo elemento “Dune” nell’array.

Moduli (import/export)

A partire dalla versione ES6, abbiamo una sintassi nativa per i moduli che ci permette di disporre il codice in file separati. I moduli aiutano a evitare conflitti di nome, poiché le variabili, funzioni e classi definite in un modulo sono limitate al modulo stesso a meno che non vengano esportate. Un altro vantaggio è la riusabilità: possiamo facilmente riciclare il codice per altri progetti. Infine bisogna considerare che i moderni strumenti di build possono ottimizzare i moduli per ridurre le dimensioni del codice in produzione.
Ad essere sincero tendo ad usarlo solo per snippet molto specifici, ma anche questo giocattolino è utile. In poche parole si tratta di utilizzare le parole chiave import ed export per suddividere il codice JavaScript in parti più piccole e riutilizzabili.  Facciamo un esempio al volo, supponiamo di avere due file JavaScript: modulo.js e main.js. Possiamo scrivere una funzione nel primo file e chiamarla nel secondo.

// codice nel file modulo.js
export const ciaomondo = () => console.log(“Ciao Mondo!”);

// codice nel file main.js
import { ciaomondo } from ‘./modulo.js’; saluta();

Esistono diversi modi per esportare ed importare codice dai moduli in ES6: la tecnica nominativa che consente di esportare/importare più variabili, funzioni o classi da un modulo e l’impostazione di default che permette di esportare/importare un singolo valore, funzione o classe come esportazione predefinita del modulo. Vediamo un esempio di esportazione nominativa:

// file modulo.js
export const titolo = “Il Signore degli Anelli”;
export const pagine = 1408;
export function descrizione() {
console.log(“Un’epica saga fantasy!”);
}
export class Libro {
constructor(titolo, pagine) {
this.titolo = titolo;
this.pagine = pagine;
}
}

Passiamo all’importazione nominativa

// file main.js
import { titolo, pagine, descrizione } from ‘./modulo.js’;
console.log(titolo); // Il Signore degli Anelli
console.log(pagine); // 1408
descrizione(); // Un’epica saga fantasy!

Adesso facciamo un esempio con l’esportazione di Default

// file modulo.js
export default class Libro {
constructor(titolo, pagine) {
this.titolo = titolo;
this.pagine = pagine;
}
}

Per importare l’esportazione di default di un modulo, si utilizza un nome a scelta senza le parentesi graffe.

// file main.js
import Libro from ‘./modulo.js’;
const libro = new Libro(“Il Signore degli Anelli”, 1408);
console.log(libro.titolo); // Il Signore degli Anelli
console.log(libro.pagine); // 1408

Un’altra funzione interessante è l’importazione dinamica dei moduli tramite Caricamento Asincrono. Volendo possiamo caricare moduli solo quando necessario, piuttosto che al caricamento iniziale dell’applicazione. Questo può migliorare le prestazioni dell’applicazione, specialmente per progetti di grandi dimensioni. Facciamo un esempio:

// supereroi.js
export const eroi= {
superman: “Clark Kent”,
batman: “Bruce Wayne”,
wonderWoman: “Diana Prince”,
};
export function getEroiAlias(name) {
return eroi[name];
}

Il codice presente in supereroi.js esporta un oggetto heroes contenente i nomi e gli alias dei supereroi. Inoltre esporta anche una funzione getHeroAlias che restituisce l’alias di ogni supereroe a partire dal nome. Vediamo come funziona il codice main.js che invece importa il modulo:

// main.js
document.getElementById(‘loadButton’).addEventListener(‘click’, () => {
// Importa dinamicamente il modulo supereroi.js
import(‘./supereroi.js’)
.then(module => {
// Usa il modulo importato
const eroi= module.eroi;
const getEroiAlias = module.getEroiAlias ;
console.log(eroi);
// Output: { superman: “Clark Kent”, batman: “Bruce Wayne”, wonderWoman: “Diana Prince” }
console.log(getEroiAlias (‘batman’));
// Output: Bruce Wayne
})
.catch(err => {
console.error(‘Errore nell\’importazione del modulo:’, err);
});
});

Il codice aggiunge un event listener a un pulsante con l’id loadButton. Quando al clic viene importato dinamicamente il modulo supereroi.js usando import(‘./supereroi.js’). Dopo l’importazione, viene utilizzato il modulo importato per accedere agli eroi e alla funzione getEroiAlias .
I moduli rappresentano un’evoluzione significativa per JavaScript, rendendo il linguaggio più robusto e adatto a progetti su larga scala. La capacità di importare ed esportare codice in modo nativo permette di costruire applicazioni più modulari e mantenibili, una caratteristica fondamentale nello sviluppo moderno.

Una scelta obbligata

Questo articolo offre solo una piccola panoramica delle novità presenti nel JavaScript moderno. Si tratta di appunti presi nel corso del tempo e che ho provato a mettere in ordine, ma ci sarebbero da dire molte altre cose, soprattutto in relazione con gli strumenti basati sul linguaggio.
Molti dei framework e librerie JavaScript, come React, Vue, Phaser e Node, sfruttano le funzionalità di JavaScript Modern. Un altro campo in cui si è diffuso è quello dello sviluppo di applicazioni al di fuori del web. Con tecnologie come React Native per mobile ed Electron per desktop, si possono creare applicazioni di tutto rispetto. Questo significa che possiamo scrivere una sola volta il nostro codice e distribuirlo su diverse piattaforme. React Native, ad esempio, utilizza JS Modern per permettere agli sviluppatori di costruire applicazioni mobile native per iOS e Android utilizzando la stessa base di codice. Allo stesso modo, Electron permette di creare applicazioni desktop che funzionano su Windows, macOS e Linux.
Seguire le varie evoluzioni del linguaggio può essere effettivamente abbastanza impegnativo per i developer abitudinari (come il sottoscritto), ma a prescindere dalla decantata leggibilità e facilità di manutenzione, adottare la nuova sintassi ci aiuta a restare al passo con i tempi.

Share:

Leave a reply

*