Home
5 risposte su… usare JavaScript da vero professionista

27 Aprile 2021

5 risposte su… usare JavaScript da vero professionista

di

Questo linguaggio, usato pervasivamente sul web, è diventato un componente fondamentale nel bagaglio professionale di molti programmatori e una opportunità importante per chi si inizia a scrivere codice per mestiere. Vediamo qualche esempio di tecniche utilizzate per usarlo al meglio.

Di che cosa parliamo

  1. Come scrivere un semplice server HTTP o HTTPS con Node.js
  2. Come gestire pacchetti con npm
  3. Come usare Shadow DOM per trasformare componenti custom scritti con JavaScript in veri componenti web
  4. Come funzionano gli operatori di confronto in JavaScript
  5. Come è fatta la classe RegExp in JavaScript

1. Come scrivere un semplice server HTTP o HTTPS con Node.js

I moduli http, https e http2 di Node sono implementazioni complete ma di livello relativamente basso dei protocolli HTTP. Definiscono API complete per l’implementazione di client e server HTTP.

Il modo più semplice per effettuare una semplice richiesta HTTP GET è con http.get() o https.get(). Il primo argomento di queste funzioni è l’URL da recuperare. Se si tratta di un URL http://, dovrai impiegare il modulo http, e se si tratta di un URL https:// dovrai impiegare il modulo https. Il secondo argomento è una funzione callback che dovrà essere richiamata con un oggetto IncomingMessage quando la risposta del server ha iniziato ad arrivare.

Quando viene richiamata la funzione callback, lo stato HTTP e le intestazioni sono disponibili, ma il corpo potrebbe non essere ancora pronto. L’oggetto IncomingMessage è uno stream Readable.

Facciamo ora un esempio per vedere come venga utilizzato http.get().

Iscriviti alla nostra newsletter

http.get() e https.get() sono varianti leggermente semplificate delle funzioni più generali http.request() e https.request(). La seguente funzione postJSON() mostra come utilizzare https.request() per eseguire una richiesta HTTPS POST che includa una richiesta JSON. La richiesta si aspetta una risposta JSON e restituisce una Promise che soddisfa la versione, sottoposta a parsing, di quella risposta:

const https = require(“https”);
/*
 * Converti l’oggetto body in una stringa JSON, poi inviala con HTTPS POST
 * all’endpoint specificato sull’host specificato. Quando arriva la risposta,
 * sottoponi a parsing il corpo della risposta come JSON e risolvi la Promise
 * restituita con quel valore, dopo il parsing.
 */
function postJSON(host, endpoint, body, port, username, password) {
// Restituisce immediatamente un oggetto Promise, quindi richiama resolve o reject
// quando la richiesta HTTPS ha esito positivo o negativo.
return new Promise((resolve, reject) => {
    // Converte l’oggetto body in una stringa
    let bodyText = JSON.stringify(body);
    // Configura la richiesta HTTPS
    let requestOptions = {
        method: “POST”,    // Oppure “GET”, “PUT”, “DELETE”, ecc.
        host: host,        // L’host cui connettersi
        path: endpoint,    // Il percorso dell’URL
        headers: {         // Intestazioni HTTP per la richiesta
            “Content-Type”: “application/json”,
            “Content-Length”: Buffer.byteLength(bodyText)
        }
    };
    if (port) {                     // Se viene specificata una porta,
        requestOptions.port = port; // utilizzala per la richiesta.
    }
    // Se vengono specificate le credenziali, aggiungi un’intestazione Authorization.
    if (username && password) {
        requestOptions.auth = `${username}:${password}`;
    }
    // Ora crea la richiesta in base all’oggetto di configurazione
    let request = https.request(requestOptions);
    // Scrivi il corpo della richiesta POST e termina la richiesta.
    request.write(bodyText);
    request.end();
    // Fallisci in caso di errori della richiesta (come nessuna connessione di rete)
    request.on(“error”, e => reject(e));
    // Gestisci la risposta quando comincia ad arrivare.
    request.on(“response”, response => {
        if (response.statusCode !== 200) {
            reject(new Error(`HTTP status ${response.statusCode}`));
            // In questo caso non ci interessa il corpo della risposta,
            // ma non vogliamo che rimanga in giro in un buffer da qualche parte,
            // così mettiamo lo stream in flowing mode senza registrare
            // un gestore di “data”, in modo che il corpo venga scartato.
            response.resume();
            return;
        }
        // Vogliamo il testo, non i byte. Supponiamo che il testo
        // sia in formato JSON ma non ci interessi di controllare
        // l’intestazione Content-Type.
        response.setEncoding(“utf8”);
            // Node non ha un parser per stream JSON, per cui abbiamo letto
            // l’intero corpo della risposta in una stringa.
            let body = ““;
            response.on(“data”, chunk => { body += chunk; });
            // E ora gestisci la risposta, quando è completata.
            response.on(“end”,() => {             // Quando la risposta è finita,
                try {                             // cerca di sottoporre a parsing come JSON
                    resolve(JSON.parse(body));    // e risolvi il risultato.
                } catch (e) {                     // Oppure, se qualcosa va storto,
                    reject(e);                    // rifiuta con l’errore
                }
            });
        });
    });
}

Oltre a effettuare richieste HTTP e HTTPS, i moduli http e https consentono anche di scrivere server che rispondono a tali richieste. L’approccio di base è il seguente.

  • Creare un nuovo oggetto Server.
  • Richiamare il suo metodo listen() per metterlo in ascolto di richieste sulla porta specificata.
  • Registrare un gestore per gli eventi request, utilizzare tale gestore per leggere la richiesta del client (in particolare la proprietà request.url) e inviare la risposta.

Il codice che segue crea un semplice server HTTP che serve file statici dal filesystem locale e implementa anche un endpoint di debug che risponde alla richiesta di un client facendo eco a quella richiesta.

// Questo è un semplice server HTTP statico che serve file da una determinata
// directory. Implementa anche uno speciale endpoint /test/mirror che
// rinvia la richiesta in arrivo, cosa che può essere utile durante il debug dei client.
const http = require(“http”);    // Usa “https” se hai un certificato
const url = require(“url”);      // Per sottoporre a parsing gli URL
const path = require(“path”);    // Per manipolare i percorsi del filesystem
const fs = require(“fs”);        // Per leggere i file
// Serve file dalla directory root specificata tramite un server HTTP che
// ascolta sulla porta specificata.
function serve(rootDirectory, port) {
    let server = new http.Server();    // Crea un nuovo server HTTP.
    server.listen(port);               // Ascolta sulla porta specificata
    console.log(“Listening on port”, port);
    // Quando arrivano le richieste, gestiscile con questa funzione
    server.on(“request”, (request, response) => {
        // Ottiene la parte del percorso dell’URL della richiesta,
        // ignorando tutti i parametri aggiunti per la query.
        let endpoint = url.parse(request.url).pathname;
        // Se la richiesta era per “/test/mirror”, rispedisci la richiesta
        // così com’è. Utile per visualizzare le intestazioni e il corpo della richiesta.
        if (endpoint === “/test/mirror”) {
            // Imposta l’intestazione della risposta
            response.setHeader(“Content-Type”, “text/plain; charset = UTF-8”);
            // Specifica il codice di stato della risposta
            response.writeHead(200);    // 200 OK
            // Inizia il corpo della risposta con la richiesta
            response.write(`${request.method} ${request.url} HTTP/${
                request.httpVersion
            }\r\n`);
            // Emette le intestazioni della richiesta
            let headers = request.rawHeaders;
            for(let i = 0; i < headers.length; i += 2) {
                response.write(`${headers[i]}: ${headers[i + 1]}\r\n`);
            }
            // Termina le intestazioni con una riga vuota in più
            response.write(“\r\n”);
            // Ora abbiamo bisogno di copiare il corpo della richiesta sul corpo della
            // risposta. Dato che sono entrambi stream, possiamo impiegare una pipe
            request.pipe(response);
        }
        // Altrimenti, servi un file dalla directory locale.
        else {
            // Associa l’endpoint a un file nel filesystem locale
            let filename = endpoint.substring(1);    // elimina i / iniziali
            // Non permettere “../” nel percorso, perché sarebbe un problema
            // di sicurezza servire qualsiasi cosa fuori dalla directory root.
            filename = filename.replace(/\.\.\//g, ““);
            // Ora converti il nome di file da relativo ad assoluto
            filename = path.resolve(rootDirectory, filename);
            // Ora indovina il tipo di contenuto del file in base all’estensione
            let type;
            switch(path.extname(filename)) {
            case “.html”:
            case “.htm”: type = “text/html”; break;
            case “.js”: type = “text/javascript”; break;
            case “.css”: type = “text/css”; break;
            case “.png”: type = “image/png”; break;
            case “.txt”: type = “text/plain”; break;
            default: type = “application/octet-stream”; break;
            }
                let stream = fs.createReadStream(filename);
                stream.once(“leggibile”,() => {
                    // Se lo stream diventa leggibile, imposta
                    // l’intestazione Content-Type e lo stato 200 OK. Quindi invia
                    // in pipe lo stream sulla risposta. La pipe richiamerà
                    // automaticamente response.end() al termine dello stream.
                    response.setHeader(“Content-Type”, type);
                    response.writeHead(200);
                    stream.pipe(response);
                });
                stream.on(“error”, (err) => {
                    // Invece, se otteniamo un errore tentando di aprire lo stream
                    // allora il file probabilmente non esiste o non è leggibile.
                    // Invia una risposta plain-text 404 Not Found con il
                    // messaggio di errore.
                    response.setHeader(“Content-Type”, “text/plain; charset = UTF-8”);
                    response.writeHead(404);
                    response.end(err.message);
                });
            }
        });
    }
    // Quando veniamo richiamati dalla riga di comando, richiama la funzione serve()
    serve(process.argv[2] || “/tmp”, parseInt(process.argv[3]) || 8000);

I moduli built-in di Node sono tutto ciò di cui hai bisogno per scrivere semplici server HTTP e HTTPS. Nota, tuttavia, che i server di produzione in genere non vengono realizzati direttamente su questi moduli. La maggior parte dei server non banali viene implementata utilizzando librerie esterne, come il framework Express, che forniscono tutto il middleware e gli altri servizi di cui gli sviluppatori web backend hanno bisogno.

Torna all’inizio.

2. Come gestire pacchetti con npm

Nello sviluppo di software moderno, è facile che qualsiasi programma di dimensioni non banali dipenda da librerie software prodotte da terze parti. Se stai scrivendo un server web in Node, per esempio, potresti utilizzare il framework Express. E se stai creando un’interfaccia utente da visualizzare in un browser web, puoi impiegare un frame di frontend come React o LitElement o Angular.

Un package manager aiuta a trovare e installare tutti i pacchetti di terze parti necessari. Altrettanto importante, un package manager tiene traccia dei pacchetti dai quali dipende il tuo codice, e salva queste informazioni in un file in modo che, quando qualcun altro vorrà provare il tuo programma, potrà scaricare il tuo codice e il tuo elenco di dipendenze, e poi utilizzare il proprio package manager per installare tutti i pacchetti di terze parti di cui il tuo codice ha bisogno.

npm è il package manager fornito in bundle con Node.

Se stai provando il progetto JavaScript di qualcun altro, allora una delle prime cose che spesso dovrai fare dopo il download del codice è digitare npm install. Questo comando legge le dipendenze elencate nel file package.json, scarica i pacchetti di terze parti richiesti dal progetto e li salva in una directory node_modules/.

Puoi anche digitare npm install <nome-pacchetto> per installare un particolare pacchetto nella directory node_modules/ del tuo progetto:

$npm install express

Oltre a installare il pacchetto, npm crea anche una registrazione delle dipendenze nel file package.json del progetto. Questa registrazione delle dipendenze permette agli altri utenti di installare quelle dipendenze semplicemente digitando npm install.

L’altro tipo di dipendenza è dagli strumenti di sviluppo che sono necessari per consentire ad altri sviluppatori di lavorare sul tuo progetto, ma che non sono effettivamente necessari per eseguire il codice. Se un progetto utilizza Prettier, per esempio, per garantire che tutto il suo codice sia formattato in modo coerente, Prettier è una dipendenza di sviluppo e puoi installarne e registrarne una con --save-dev:

$npm install --save-dev prettier

A volte potresti voler installare strumenti di sviluppo a livello globale, in modo che essi siano accessibili ovunque, anche per il codice che non fa parte di un progetto formale, con un file package.json e una directory node_modules/. Per questo puoi impiegare l’opzione -g (globale):

$ npm install -g eslint jest
/usr/local/bin/eslint -> /usr/local/lib/node_modules/eslint/bin/eslint.js
/usr/local/bin/jest -> /usr/local/lib/node_modules/jest/bin/jest.js
+ [email protected]
+ [email protected]
added 653 packages from 414 contributors in 25.596s
$ which eslint /usr/local/bin/eslint
$ which jest /usr/local/bin/jest

Oltre al comando install, npm supporta i comandi uninstall e update, che disinstallano e aggiornano i pacchetti. npm offre anche un interessante comando audit, che puoi utilizzare per trovare e correggere le vulnerabilità di sicurezza nelle tue dipendenze:

$ npm audit --fix
=== npm audit security report ===
found 0 vulnerabilities
in 876354 scanned packages

Quando installi uno strumento come ESLint in modo locale, per un progetto,lo script eslint finisce in ./node_modules/.bin/eslint, il che rende il comando scomodo da eseguire. Fortunatamente, npm è fornito in bundle con un comando noto come npx, che puoi impiegare per eseguire strumenti installati localmente con comandi come npx eslint o npx jest (e se impieghi npx per richiamare uno strumento che non è stato ancora installato, lo installerà per te).

La società che produce npm gestisce anche il repository di pacchetti https://npmjs.com, che contiene centinaia di migliaia di pacchetti open source. Ma non c’è obbligo di impiegare il package manager npm per accedere a questo repository di pacchetti. Le alternative includono yarn e pnpm.

Torna all’inizio.

3. Come usare Shadow DOM per trasformare componenti custom scritti con JavaScript in veri componenti web

Per trasformare un elemento custom in un vero componente web, si deve usare il potente meccanismo di incapsulamento noto come shadow DOM.

Shadow DOM consente di allegare una shadow root, una radice ombra a un elemento custom (e anche agli elementi <div>, <span>, <body>, <article>, <main>, <nav>, <header>, <footer>, <section>, <p>, <blockquote>, <aside> e da <h1> a <h6>) noto come shadow host. Gli elementi shadow host, come tutti gli elementi HTML, sono già la radice di un normale albero del DOM di elementi discendenti e nodi di testo. Una shadow root è la radice di un altro albero, più privato, di elementi discendenti che germogliano dallo shadow host e può essere considerata come un minidocumento distinto.

La parola shadow, ombra, fa riferimento al fatto che gli elementi che discendono da una shadow root si nascondono nell’ombra: non fanno parte del normale albero del DOM, non compaiono nell’array children del loro elemento host e non sono visitati dai normali metodi di attraversamento del DOM, come querySelec- tor(). Per contrasto, i normali componenti figli del DOM di uno shadow host sono a volte chiamati light DOM.

Per comprendere lo scopo dello shadow DOM, immagina gli elementi HTML <audio> e <video>: visualizzano un’interfaccia utente non banale per il controllo della riproduzione multimediale, ma i pulsanti Play e Pause e altri elementi dell’interfaccia utente non fanno parte dell’albero del DOM e non possono essere manipolati dal codice JavaScript. Dato che i browser web sono progettati per visualizzare codice HTML, è naturale che i produttori di browser desiderino visualizzare interfacce utente interne come queste utilizzando codice HTML. In pratica, la maggior parte dei browser fa qualcosa del genere da molto tempo e lo shadow DOM lo rende solo una parte standard della piattaforma web.

Incapsulamento dello shadow DOM

La caratteristica chiave dello shadow DOM è l’incapsulamento che fornisce. I discendenti di una shadow root sono nascosti e indipendenti dal normale albero del DOM, quasi come se fossero in un documento a parte. Esistono tre tipi molto importanti di incapsulamento forniti dallo shadow DOM.

  • Come già accennato, gli elementi nello shadow DOM sono nascosti ai normali metodi del DOM come querySelectorAll(). Quando una shadow root viene creata e collegata al suo shadow host, può essere creata in modalità aperta o chiusa, open o closed. Una shadow root chiusa è completamente sigillata e inaccessibile. Più comunemente, tuttavia, le shadow root vengono create in modalità aperta, ovvero lo shadow host ha una proprietà shadowRoot che JavaScript può utilizzare per accedere agli elementi della shadow root, se ha qualche motivo per farlo.
  • Gli stili definiti sotto una shadow root sono privati di quell’albero e non influenzeranno mai gli elementi del DOM all’esterno. Una shadow root può definire stili predefiniti per il suo elemento host, ma essi saranno sovrascritti dagli stili del DOM in chiaro. Allo stesso modo, gli stili del DOM che si applicano all’elemento shadow host non hanno effetto sui discendenti della shadow root. Gli elementi nello shadow DOM erediteranno aspetti come le dimensioni del carattere e il colore di sfondo dal DOM in chiaro, e gli stili nello shadow DOM possono scegliere di utilizzare le variabili CSS definite nel DOM in chiaro. Nella maggior parte dei casi, tuttavia, gli stili del DOM in chiaro e gli stili dello shadow DOM sono completamente indipendenti: l’autore di un componente web e l’utilizzatore di un componente web non devono preoccuparsi di collisioni o conflitti tra i loro fogli di stile. Essere in grado di definire la visibilità (scope) CSS in questo modo è forse la caratteristica più importante dello shadow DOM.
  • Alcuni eventi (come load) che si verificano all’interno dello shadow DOM sono limitati allo shadow DOM stesso. Altri, tra cui gli eventi focus, del mouse e della tastiera risalgono e fuoriescono. Quando un evento che ha origine nello shadow DOM attraversa il confine e inizia a propagarsi nel DOM in chiaro, la sua proprietà target viene modificata nell’elemento shadow host, in modo che sembri aver avuto origine direttamente su tale elemento.

Slot dello shadow DOM e figli del DOM in chiaro

Un elemento HTML che è uno shadow host ha due alberi di discendenti. Uno è l’array children[] (i normali discendenti del DOM in chiaro dell’elemento host) e l’altro è la shadow root con tutti i suoi discendenti. Potresti chiederti in quale modo due distinti alberi di contenuti possano essere visualizzati all’interno dello stesso elemento host. Ecco come funziona.

  • I discendenti della shadow root vengono sempre visualizzati all’interno dello shadow host.
  • Se tali discendenti includono un elemento , i normali figli del DOM in chiaro dell’elemento host vengono visualizzati come se fossero figli di quello <slot>, sostituendo qualsiasi contenuto dello shadow DOM presente nello slot. Se lo shadow DOM non include uno <slot>, qualsiasi contenuto del DOM in chiaro dell’host non viene mai visualizzato. Se lo shadow DOM ha uno , ma l’host shadow non ha elementi figli del DOM in chiaro, viene visualizzato, come impostazione di default, il contenuto dello shadow DOM dello slot.
  • Quando il contenuto del DOM in chiaro viene visualizzato all’interno di uno slot dello shadow DOM, diciamo che quegli elementi sono stati distribuiti, ma è importante comprendere che gli elementi non diventano effettivamente parte dello shadow DOM. Possono ancora essere interrogati con querySelector(), e appaiono ancora nel DOM in chiaro come elementi figli o discendenti dell’elemento host.
  • Se lo shadow DOM definisce più di uno e nomina quegli slot con un attributo name, allora i figli dello shadow host possono indicare in quale slot vorrebbero apparire, specificando un attributo slot=”slotname”.

API dello shadow DOM

Nonostante tutta la sua potenza, lo shadow DOM non ha molte API per JavaScript. Per attivare un elemento DOM in chiaro in uno shadow host, basta richiamare il suo metodo attachShadow(), passandogli come unico argomento {mode:”open”}. Questo metodo restituisce un oggetto shadow root e stabilisce anche l’oggetto come valore della proprietà shadowRoot dell’host. L’oggetto shadow root è un DocumentFragment e puoi impiegare i metodi del DOM per aggiungervi contenuti o semplicemente impostare la sua proprietà innerHTML su una stringa di codice HTML.

Se il tuo componente web ha bisogno di sapere quando è cambiato il contenuto del DOM in chiaro di uno <slot> di uno shadow DOM, può registrare direttamente un listener per gli eventi slotchanged sull’elemento <slot>.

Torna all’inizio.

4. Come funzionano gli operatori di confronto in JavaScript

Gli operatori di confronto verificano l’ordine relativo (numerico o alfabetico) dei loro due operandi.

  • Minore di (<) – L’operatore < restituisce true se il suo primo operando è minore del secondo; in caso contrario, restituisce false.
  • Maggiore di (>) – L’operatore > restituisce true se il suo primo operando è maggiore del secondo; in caso contrario, restituisce false.
  • Minore o uguale (<=) – L’operatore <= restituisce true se il suo primo operando è minore o uguale al secondo; in caso contrario, restituisce false.
  • Maggiore o uguale (>=) – L’operatore >= restituisce true se il suo primo operando è maggiore o uguale al secondo; in caso contrario, restituisce false.

Gli operandi di questi operatori di confronto possono essere di qualsiasi tipo. Tuttavia, il confronto può essere eseguito solo su numeri e stringhe, quindi gli operandi che non sono numeri o stringhe vengono convertiti. Il confronto e la conversione avvengono nel seguente modo.

  • Se uno dei due operandi è un oggetto, l’oggetto viene convertito in un valore primitivo; se il suo metodo valueOf() restituisce un valore primitivo, viene usato quel valore. Altrimenti, viene utilizzato il valore restituito del suo metodo toString().
  • Se, dopo una qualsiasi conversione richiesta da un oggetto a un valore primitivo, entrambi gli operandi sono stringhe, le due stringhe vengono confrontate utilizzando l’ordine alfabetico, definito dall’ordine numerico dei valori Unicode a 16 bit che compongono le stringhe.
  • Se, dopo la conversione da oggetto a valore primitivo, almeno un operando non è una stringa, entrambi gli operandi vengono convertiti in numeri e confrontati numericamente. 0 e -0 sono considerati uguali. Infinity è maggiore di qualsiasi numero diverso da se stesso e, analogamente, -Infinity è minore di qualsiasi numero diverso da se stesso. Se uno degli operandi è (o viene convertito in) NaN, l’operatore di confronto restituisce sempre false. Sebbene gli operatori aritmetici non consentano di impiegare combinazioni di valori BigInt e normali numeri, gli operatori di confronto consentono di confrontare numeri e BigInt.

Ricorda che in JavaScript le stringhe sono sequenze di valori interi a 16 bit e che il confronto tra stringhe è solo un confronto numerico dei valori contenuti nelle due stringhe. L’ordine della codifica numerica Unicode potrebbe non corrispondere all’ordine tradizionale utilizzato in una particolare lingua. Nota, in particolare, che il confronto tra stringhe distingue tra lettere maiuscole e minuscole e che tutte le lettere ASCII maiuscole sono minori di tutte le lettere ASCII minuscole.

Questa regola può causare risultati inaspettati. Per esempio, secondo l’operatore <, la stringa Zoo precede la stringa abaco. Per usare un algoritmo più pratico di confronto fra stringhe, prova il metodo String.localeCompare(), che tiene in considerazione le definizioni di ordine alfabetico specifiche della lingua. Per fare confronti senza distinguere tra lettere maiuscole e minuscole, puoi convertire le stringhe in lettere minuscole o maiuscole usando String.toLowerCase() o String.toUpperCase(). Per usare uno strumento di confronto fra stringhe più generale e meglio localizzato, utilizza la classe Intl.Collator.

Sia l’operatore + sia gli operatori di confronto si comportano in modo diverso per gli operandi numerici e stringa. + favorisce le stringhe: se uno degli operandi è una stringa esegue un concatenamento. Gli operatori di confronto favoriscono i numeri ed eseguono un confronto tra stringhe solo se entrambi gli operandi sono stringhe:

1+ 2         // => 3: addizione.
“1” + “2”    // => “12”: concatenamento.
“1” + 2      // => “12”: 2 viene convertito in “2”.
11 < 3       // => false: confronto numerico.
“11” < “3”   // => true: confronto tra stringhe.
“11” < 3     // => false: confronto numerico, “11” viene convertito in 11.
“one” < 3    // => false: confronto numerico, “one” convertito in NaN.

Infine, nota che gli operatori <= (minore o uguale) e >= (maggiore o uguale) non si basano sugli operatori di uguaglianza o uguaglianza rigorosa per determinare se due valori sono uguali. Al contrario, l’operatore minore o uguale è definito semplicemente come non maggiore di e l’operatore maggiore o uguale è definito come non minore di. Si verifica un’eccezione solo quando uno degli operandi è (o viene convertito in) NaN, nel qual caso tutti e quattro gli operatori di confronto restituiscono false.

L’operatore in

L’operatore in si aspetta sul lato sinistro un operando che sia una stringa, un Symbol o un valore che possa essere convertito in una stringa e si aspetta che l’operando a destra sia un oggetto. Restituisce true se il valore sul lato sinistro è il nome di una proprietà dell’oggetto a destra. Per esempio:

let point = {x: 1, y: 1};    // Definisce un oggetto
“x” in point                 // => true: l’oggetto ha una proprietà denominata “x”
“z” in point                 // => false: l’oggetto non ha la proprietà “z”.
“toString” in point          // => true: l’oggetto eredita il metodo toString
let data = [7, 8, 9];        // Un array con elementi (indici) 0, 1 e 2
“0” in data                  // => true: l’array ha un elemento “0”
1 in data                    // => true: i numeri vengono convertiti in stringhe
3 in data                    // => false: nessun elemento 3

L’operatore instanceof

L’operatore instanceof si aspetta a sinistra un operando che è un oggetto e a destra un operando che identifica una classe di oggetti. L’operatore restituisce true se l’oggetto a sinistra è un’istanza della classe a destra e in caso contrario restituisce false. In JavaScript, le classi di oggetti sono definite dalla funzione costruttore che le inizializza. Pertanto, l’operando a destra di instanceof dovrebbe essere una funzione. Ecco alcuni esempi:

let d = new Date();    // Crea un nuovo oggetto con il costruttore Date()
d instanceof Date      // => true: d è stato creato con Date()
d instanceof Object    // => true: tutti gli oggetti sono istanze di Object
d instanceof Number    // => false: d non è un oggetto Number
let a = [1, 2, 3];     // Crea un array con la sintassi dei letterali array
a instanceof Array     // => true: a è un array
a instanceof Object    // => true: tutti gli array sono oggetti
a instanceof RegExp    // => false: gli array non sono espressioni regolari

Nota che tutti gli oggetti sono istanze di Object. instanceof considera anche le superclassi quando decide se un oggetto è un’istanza di una determinata classe. Se l’operando alla sua sinistra non è un oggetto, instanceof restituisce false. Se il lato destro non è una classe di oggetti, lancia un’eccezione TypeError.

Per comprendere il funzionamento dell’operatore instanceof, è necessario conoscere la catena di prototipizzazione, ovvero il meccanismo di ereditarietà di JavaScript. Per valutare l’espressione o instanceof f, JavaScript valuta f.prototype, e poi cerca tale valore nella catena di prototipi di o. Se lo trova, allora o è un’istanza di f (o di una sottoclasse di f) e l’operatore restituisce true. Se f.prototype non è presente nella catena dei prototipi di o, allora significa che o non è un’istanza di f e instanceof restituisce false.

Torna all’inizio.

5. Come è fatta la classe RegExp in JavaScript

Il costruttore RegExp() accetta uno o due argomenti stringa e crea un nuovo oggetto RegExp. Il primo argomento del costruttore è una stringa che contiene il corpo dell’espressione regolare, il testo che apparirebbe tra le barre in un’espressione regolare letterale. Nota che sia i letterali stringa sia le espressioni regolari usano il carattere \ per le sequenze di escape, quindi quando si passa a RegExp() un’espressione regolare come letterale stringa, dovrai sostituire ogni carattere \ con \\. Il secondo argomento di RegExp() è opzionale.

Se fornito, indica i flag da applicare. Dovrebbe quindi essere g, i, m, s, u, y o qualsiasi combinazione di tali lettere. Per esempio:

// Trova tutti i numeri di cinque cifre in una stringa. Nota il doppio \\.
let zipcode = new RegExp(“\\d{5}”, “g”);

Il costruttore RegExp() è utile quando un’espressione regolare viene creata dinamicamente e quindi non può essere rappresentata con la sintassi letterale dell’espressione regolare. Per esempio, per cercare una stringa inserita dall’utente, è necessario creare un’espressione regolare a runtime con RegExp().

Invece di passare come primo argomento di RegExp() una stringa, puoi anche passare un oggetto RegExp. Questo permette di copiare un’espressione regolare e cambiarne i flag:

let exactMatch = /JavaScript/;
let caseInsensitive = new RegExp(exactMatch, “i”);

Proprietà di RegExp

Gli oggetti RegExp hanno le seguenti proprietà.

  • source – Proprietà di sola lettura che è il testo sorgente dell’espressione regolare: i caratteri che compaiono tra le barre in un letterale RegExp.
  • flags – Proprietà di sola lettura: una stringa che specifica l’insieme di lettere rappresentanti i flag per l’espressione regolare.
  • global – Proprietà booleana di sola lettura che è true se è impostato il flag g.
  • ignoreCase – Proprietà booleana di sola lettura che è true se è impostato il flag i.
  • multiline – Proprietà booleana di sola lettura che è true se è impostato il flag m.
  • dotAll – Proprietà booleana di sola lettura che è true se è impostato il flag s.
  • unicode – Proprietà booleana di sola lettura che è true se è impostato il flag u.
  • sticky – Proprietà booleana di sola lettura che è true se è impostato il flag y.
  • lastIndex – Questa proprietà è un numero intero di lettura/scrittura. Per pattern con i flag g o y specifica la posizione del carattere in cui deve iniziare la ricerca successiva. È utilizzato dai metodi exec() e test() descritti nei prossimi due paragrafi.

test()

Il metodo test() della classe RegExp è il modo più semplice per utilizzare un’espressione regolare. Prende come argomento una stringa e restituisce true se la stringa corrisponde al pattern o false, se non corrisponde.

test() funziona semplicemente richiamando il (molto più complicato) metodo exec() descritto nel prossimo paragrafo e restituendo true se exec() restituisce un valore non nullo. Per questo motivo, se utilizzi test() con un RegExp che utilizza i flag g o y, il suo comportamento dipende dal valore della proprietà lastIndex, che può cambiare improvvisamente. dell’oggetto RegExp.

exec()

Il metodo exec() dell’oggetto RegExp è il modo più generale e potente per utilizzare le espressioni regolari. Prende un singolo argomento stringa e cerca una corrispondenza in quella stringa. Se non individua alcuna corrispondenza, restituisce null. Se individua una corrispondenza, restituisce un array proprio come l’array restituito dal metodo match() per le ricerche non globali.

L’elemento 0 dell’array contiene la stringa che corrisponde all’espressione regolare; ogni elemento successivo dell’array contiene le sottostringhe che corrispondono ai gruppi con nome. L’array restituito ha anche alcune proprietà:

  • la proprietà index contiene la posizione del carattere in cui si è verificata la corrispondenza;
  • la proprietà input specifica la stringa che è stata cercata;
  • la proprietà groups, se definita, fa riferimento a un oggetto che contiene le sottostringhe corrispondenti a qualsiasi gruppo con nome.

A differenza del metodo match() della classe String, exec() restituisce lo stesso tipo di array indipendentemente dal fatto che l’espressione regolare abbia o meno il flag g globale. Ricorda che match() restituisce un array di corrispondenze quando gli viene passata un’espressione regolare globale.

exec(), al contrario, restituisce sempre una singola corrispondenza e fornisce informazioni complete su essa. Quando exec() viene richiamato su un’espressione regolare che ha sia il flag g sia il flag y, consulta la proprietà lastIndex dell’oggetto RegExp per determinare dove iniziare la ricerca (e se è impostato il flag y, vincola la ricerca in modo che inizi da quella posizione).

Per un oggetto RegExp appena creato, lastIndex è 0 e la ricerca parte dall’inizio della stringa. Ma ogni volta che exec() individua una corrispondenza, aggiorna la proprietà lastIndex all’indice del carattere immediatamente successivo il testo individuato. Se exec() non riesce a individuare una corrispondenza, reimposta lastIndex a 0. Questo comportamento consente di richiamare exec() ripetutamente per individuare tutte le corrispondenze dell’espressione regolare in una stringa. Per esempio, il ciclo nel codice seguente verrà eseguito due volte:

let pattern = /Java/g;
let text = “JavaScript > Java”;
let match;
while((match = pattern.exec(text)) !== null) {
    console.log(`Matched ${match[0]} at ${match.index}`);
    console.log(`Next search begins at ${pattern.lastIndex}`);
}

Torna all’inizio.

Questo articolo richiama contenuti da JavaScript - la guida definitiva.

Immagine di apertura di Jexo su Unsplash.

L'autore

  • David Flanagan
    David Flanagan programma con JavaScript e ne divulga e documenta le evoluzioni dal 1995. Laureato in informatica e ingegneria al Massachusetts Institute of Technology, lavora come software engineer per VMware.

Iscriviti alla newsletter

Novità, promozioni e approfondimenti per imparare sempre qualcosa di nuovo

Gli argomenti che mi interessano:
Iscrivendomi dichiaro di aver preso visione dell’Informativa fornita ai sensi dell'art. 13 e 14 del Regolamento Europeo EU 679/2016.

Corsi che potrebbero interessarti

Tutti i corsi
Comunicazione-digitale-food-wine-cover Corso Online

Comunicazione digitale Food & Wine - Iniziare Bene

con Barbara Sgarzi

Per il settore enogastronomico le reti sociali sono una grande opportunità per raccontarsi e trasmettere la qualità di un prodotto o di un locale. Se vuoi capire come farlo al meglio, il corso di Barbara Sgarzi può aiutarti.

progettare-una-landing-page-cover-2 Corso Online

Progettare una Landing Page - Che Funziona

con Valentina Di Michele

Vuoi migliorare l'efficacia dei testi di una landing page? Valentina Di Michele ti insegna cosa fare e cosa evitare per progettare i contenuti di una pagina che converte.


Libri che potrebbero interessarti

Tutti i libri

JavaScript - la guida definitiva

Dalle basi del linguaggio alle tecniche avanzate

58,65

84,89€ -31%

47,41

49,90€ -5%

34,99

di David Flanagan