Il 0% ha trovato utile questo documento (0 voti)
0 visualizzazioni

06 Javascript

Il documento fornisce un'introduzione a JavaScript, un linguaggio di scripting client-side creato nel 1995, che ha rivoluzionato le pagine web rendendole interattive. Viene descritto il suo sviluppo, le caratteristiche principali come la tipizzazione debole e dinamica, e l'importanza di JavaScript nel contesto della programmazione web moderna, inclusi utilizzi server-side e database. Inoltre, il documento esplora variabili, funzioni, e il controllo del flusso, evidenziando concetti come closure e scope.
Copyright
© © All Rights Reserved
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
0 visualizzazioni

06 Javascript

Il documento fornisce un'introduzione a JavaScript, un linguaggio di scripting client-side creato nel 1995, che ha rivoluzionato le pagine web rendendole interattive. Viene descritto il suo sviluppo, le caratteristiche principali come la tipizzazione debole e dinamica, e l'importanza di JavaScript nel contesto della programmazione web moderna, inclusi utilizzi server-side e database. Inoltre, il documento esplora variabili, funzioni, e il controllo del flusso, evidenziando concetti come closure e scope.
Copyright
© © All Rights Reserved
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 83

Fondamenti del Web

Ingegneria del Software e Fondamenti Web

Corso di Laurea in Ingegneria


Informatica e dell’Automazione
Anno Accademico 2023/2024

Prof. Antonio Ferrara

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
6
JavaScript
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Introduzione a
JavaScript
È un linguaggio di scripting client-side introdotto nel 1995 nel browser Netscape Navigator
per rendere le pagine Web “attive” e dinamiche

Creato da Brendan Eich (cofondatore di Mozilla)


Standardizzato nel 1997 da ECMA con il nome di ECMAScript
Successivamente JavaScript o altri “dialetti” di ECMAScript sono stati adottati da tutti gli altri browser

Nel tempo ha reso le pagine web davvero moderne:


• permettendo di interagire con esse senza ricaricarle ad ogni azione
• fornendo ai siti web diverse forme di interattività

ECMAScript 3 è stata la versione più utilizzata, fra il 2000 e il 2010, nel periodo di massima ascesa di JavaScript
La maggior parte dei miglioramenti attesi sono arrivati nel 2015 con ECMAScript 6

Attualmente è l’unico linguaggio di programmazione che i browser possono eseguire nativamente


(DART sta cercando di affermarsi, ma attualmente è supportato solo se compilato come JavaScript)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Introduzione a
JavaScript
JavaScript è famoso per essere stato realizzato in soli 10 giorni

Molte scelte sono state effettuate per ragioni di marketing e non per ragioni
tecniche
Si pensi al nome: JavaScript non ha nulla a che fare con Java, ma si voleva
cavalcare l’onda del successo che Java stava avendo in quel periodo

"I was under marketing orders to make it look like Java but not
make it too big for its britches ... [it] needed to be a silly little brother
language." (source)
– Brendan Eich

Esiste una parte del mondo che dice cose terribili riguardo JavaScript: è un linguaggio che accetta praticamente
qualunque tipo di input, ma può interpretarlo in maniera completamente scorretta (video)
JavaScript è, infatti, estremamente liberale e permissivo: facile per i beginner ma difficile scovare dei bug (si è diffuso
l’uso di "use strict"; per utilizzare un sottoinsieme di JavaScript che evita alcuni problemi)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Introduzione a
JavaScript
Dal 2005 (dall’introduzione del termine AJAX) e insieme al suo sviluppo “fuori dal browser”, JavaScript ha conosciuto un
grande processo di standardizzazione (e un rilascio di nuove feature ogni anno)

Col tempo, infatti, l’uso di JavaScript si è esteso anche alla programmazione server-side (come vedremo con Node.js ed il
web framework Express) e alla gestione di database come MongoDB e CouchDB che lo usano come linguaggio di scripting e
query

Per capire l’importanza di JavaScript, si pensi allo stack MERN (o alle alternative MEAN e MEVN), che permette di realizzare
applicazioni web utilizzando un solo linguaggio

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Caratteristiche di
JavaScript
• È un linguaggio interpretato (es. lato client è eseguito dall’interprete del browser)

• È un linguaggio debolmente tipizzato, cioè alcuni tipi di dato subiscono casting


automatici in base alle operazioni effettuate

• Effettua un typing dinamico, cioè l’interprete assegna il tipo ad una variabile a


runtime in base al valore assegnato

• Effettua un type checking dinamico, cioè la verifica dei vincoli di un tipo è


effettuata a runtime (questo può chiaramente causare errori in fase di
esecuzione)

• È un linguaggio orientato agli oggetti basato su prototipi: un prototipo è esso


stesso un oggetto che può essere “replicato” come fosse un template (si distingue
dal concetto di classe)

• È un linguaggio ideale per la programmazione funzionale, in quanto le sue


funzioni sono oggetti first-class

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Tipi
di dati
Vuoti Il valore undefined rappresenta una variabile dichiarata ma non assegnata
Il valore null denota un non-valore, ovvero l’assenza di valore e di informazione, può
essere immaginato come un placeholder

Rappresentati in memoria come floating points a 64 bit (limite di Curiosità


Numeri
• Per un errore di im
rappresentazione e perdita di informazione per cifre decimali non finite) plementazione
nella versione iniziale
Due particolari “numeri” sono Infinity e NaN di JS (mai
corretto), il tipo di nu
ll è un oggetto,
anche se tecnicame
nte è un valore
Sui numeri è possibile applicare i classici operatori aritmetici primitivo che rappre
senta un
+ - * / % placeholder per un o
• È possibile assegn
ggetto
are il valore
undefined a una v
ariabile (???)
Suggerimento. Usa l’operatore typeof per scoprire il tipo di un dato!

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Tipi
di dati
Stringhe Utilizzate per rappresentare del testo delimitandolo con virgolette
`Down on the sea`
"Lie on the ocean"
'Float on the ocean’
In genere, ogni carattere è rappresentato con 16 bit di memoria (a parte caratteri speciali come le emoji)

All’interno delle stringhe, il simbolo \ introduce un carattere speciale (es. \n per una nuova linea e \t per uno
spazio di tabulazione)
Ad esempio "A newline character is written like \"\\n\"."
produce A newline character is written like "\n".

Alle stringhe è possibile applicare l’operatore + per effettuare la concatenazione

Utilizzando la rappresentazione con backtick (`), le stringhe vengono definite template literals e permettono di
fare l’embedding di altri valori
Ad esempio `La metà di 100 è ${100 / 2}` produce La metà di 100 è 50

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Tipi
di dati
Booleani JavaScript consente la rappresentazione dei valori booleani true e false
In generale, valori booleani possono essere ottenuti mediante operatori di confronto fra dati (es. fra due numeri)

== != < > <= >=


Sui valori di verità dei dati booleani è possibile operare mediante gli operatori logici

&& AND logico


|| OR logico
! NOT logico
Un operatore ternario applicabile a valori booleani è l’operatore condizionale, che produce uno fra due valori in
base al valore di verità di un dato booleano
true ? 1 : 2 produce 1
false ? 1 : 2 produce 2

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Type
coercition
Come detto, nella sua flessibilità JavaScript cerca di accettare “qualunque” codice gli venga proposto
Ad esempio, in situazioni in cui gli operatori sono applicati a tipi di dati “scorretti”, JavaScript effettua
delle conversioni automatiche silenziose, dette type coercition, essendo un linguaggio weakly typed

Operando sx Operatore Operatore dx Risultato


false (booleano) + [] (array vuoto) "false" (stringa)
Consiglio
“123"(stringa) + 1 (numero) "1231" (stringa) Usare sempre === e
!== se non si
"123" (stringa) - 1 (numero) 122 (numero) vuole effettuare type
coercition
"123" (stringa) - "abc" (stringa) NaN (numero) Infatti, anziché “agg
iustare” == e !=,
ECMAScript ha decis
[] (array vuoto) + [] (array vuoto) "" (stringa vuota) o di introdurre
questi due nuovi ope
[] (array vuoto) + {} (oggetto vuoto) "[object Object]" (stringa) ratori

Anche nell’uso di == e != viene effettuata type coercition


Se si vuole effettuare una comparazione precisa senza casting è necessario usare gli equivalenti === e !===
Ad esempio, 3 == '3' produce true, mentre 3 === '3' produce false

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Espressioni
e statement
Un’espressione è un qualunque frammento di codice che produce un valore

In JavaScript, una o più espressioni, eventualmente annidate, costituiscono uno statement

1;
Normalmente uno statement ha l’obiettivo di mostrare qualcosa a schermo o cambiare lo stato interno della macchina o del
programma (side effect)

Gli statement di JavaScript terminano, in genere, con il simbolo ; che in molti casi può comunque essere omesso

Le linee di codice che iniziano con // o che sono racchiuse fra /* e */ sono considerate commenti e vengono ignorate
dall’interprete

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Variabili
e binding
Per conservare valori e cambiare lo stato di un programma, JavaScript utilizza variabili, anche dette binding
let a = 5 * 5;

Il nome di un binding è, eventualmente, seguito da un operatore di assegnazione = ed una espressione


Dopo la sua definizione, il nome di un binding può essere può essere usato come un’espressione, ed il suo valore sovrascritto
con l’operatore di assegnazione

Per avere variabili che non possono essere riassegnate è possibile utilizzare const anziché let

I nomi della variabili contengono caratteri alfanumerici e sono ammessi i simboli $ e _, ma non possono iniziare con un numero
(in JavaScript si adotta spesso la convenzione camelCase)

Attenzione. Per i nomi dei binding non possiamo utilizzare nessuna keyword riservata!
Nota bene. I binding non “contengono” valori ma puntano a valori nella memoria
Nota bene. JavaScript mette a disposizione la notazione x += y per semplificare x = x + y (valida anche con gli altri operatori
aritmetici) e la notazione x++ per semplificare x = x + 1 (valida anche per la sottrazione)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Controllo
del flusso
Normalmente, gli statement vengono eseguiti nell’ordine in cui sono scritti
Secondo le necessità del programma, è possibile variare il flusso delle istruzioni

Esecuzione condizionale Switch Iterazione per vero a Ciclo for


condizione iniziale
if (expression) { switch (value) { for (let i=0; i<N; i=i+1) {
... case a: while (expression) { ...
} else if { ... ... }
... case b: }
} else { ...
Ricorda. Puoi sempre usare le
... ...
Iterazione per vero a parole chiave continue e
} default:
condizione finale break per modificare il corso
...
} dei cicli!
do {
...
} while (expression)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Funzioni Una funzione è detta
pura se

e scope non produce side eff


dipende da side effe
chiamata più volte c
ect e non
ct, ma
on gli stessi
Le funzioni sono parti di programma che permettono di produrre side effect o restituire valori argomenti restituisc
e lo stesso
Esse possono essere associate a un binding oppure rimanere anonime risultato

È possibile richiamare la procedura di una funzione mediante il suo binding (o la sua definizione per funzioni anonime) e una coppia
di parentesi tonde al cui interno passare specifici argomenti separati da virgola (se necessari)
prompt("Inserire la password”); console.log(Math.max(2, 4) + 100);

Lo statement return stabilisce se e quali valori vengono restituiti dalla funzione (altrimenti restituisce undefined)

Dichiarazione come funzione Definizione come valore del binding Arrow function

function square(x) { const square = function(x) { const square = (x) => {


return x * x; return x * x; return x * x;
} }; };
const square = x => x * x;
Dichiarazione anonima function(x) { return x * x }

Dichiarazione anonima arrow (x) => x * x;

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Funzioni
e scope
I binding dichiarati all’interno di una funzione (o di un blocco, ad esempio quello condizionale) con let e const sono locali
e visibili solo nello scope della funzione (e delle funzioni più interne)
Binding dichiarati all’esterno di una funzione sono globali e accessibili dovunque

Nelle versioni di JavaScript precedenti al 2015 solo le funzioni “generavano” degli scope
Per questo motivo, se utilizziamo la vecchia keyword var per la dichiarazione di binding all’interno di una funzione,
otterremo il comportamento atteso, ma se utilizzata all’interno di un qualsiasi altro blocco, il binding sarà accessibile
globalmente

let x = 10; Consigli


if (true) { • Usare const ladd
ove possibile
let y = 20; • Se c’è bisogno di u
na variabile
var z = 30; riassegnabile, usare
let
• Non usare var (an
} che se lo
troverete ovunque!)
console.log(y);
console.log(z);

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Funzioni
e scope
Closure

Come visto precedentemente, una funzione può accedere al suo scope locale e allo scope globale, ma anche allo scope delle
funzioni più “esterne” in cui essa è definita
var saluto = "Buongiorno";

function saluta(persona) {
var nomeCognome = persona.nome + " " + persona.cognome;
function visualizzaSaluto() {
console.log(saluto + " " + nomeCognome);
}
visualizzaSaluto();
}

saluta({nome: "Mario", cognome: "Rossi"});

La funzione saluta invoca, internamente, la funzione visualizzaSaluto che si occupa di stampare il messaggio di saluto
utilizzando le variabili definite nello scope della funzione più esterna

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Funzioni
e scope
Closure

Consideriamo ora il seguente caso particolare, in cui una funzione crea (e restituisce) un’altra funzione
var saluto = “Buongiorno";
Anche le funzioni ch
function saluta(persona) { e restituiscono fun
zioni,
così come quelle ch
var nomeCognome = persona.nome + " " + persona.cognome; e accettano funzioni
come argomenti, so
n o dette funzioni di
return () => console.log(saluto + " " + nomeCognome); ordine superiore
}

const visualizzaSaluto = saluta({nome: "Mario", cognome: "Rossi"});


visualizzaSaluto();

La funzione saluta non si occupa direttamente di stampare il messaggio di saluto in console, ma restituisce una nuova
funzione che si occupa di stampare il messaggio
Effettivamente, quando visualizzaSaluto viene invocata, la funzione saluta è già terminata ed il suo contesto di
esecuzione non esiste più: come fa, allora, la funzione visualizzaSaluto, ad accedere alla variabile nomeCognome?

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Funzioni
e scope
Closure

Questo comportamento è possibile grazie al meccanismo della closure, che permette ad un oggetto funzione di “ricordare” le
variabili accessibili nel suo scope quando era stata creata, mantenendone un riferimento
Questo succede perché ogni contesto di esecuzione (quello globale o quello di una funzione) contiene un ambiente
lessicale che associa binding a variabili (scope) e mantiene un riferimento all'ambiente lessicale genitore (chiusura)

Esempio 1 Esempio 2 Esempio 3

let y = 1; function wrapValue(n) { function multiplier(factor) {


const f = x => x + y; return () => n; return n => n * factor;
} }
let wrap1 = wrapValue(1); let twice = multiplier(2);
let wrap2 = wrapValue(2);

La tecnica di programmazione funzionale per cui si costruiscono funzioni “parzialmente istanziate” con argomenti è detta
currying o applicazione parziale (per approfondire)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Hoisting
di funzioni e variabili
Possibilmente,
In JavaScript l’hoisting è il processo per cui le dichiarazioni di variabili e classi e evitare l’hoisting
le definizioni di funzioni vengono spostate all’inizio del loro scope

hello(); console.log(num);
// ReferenceError: Can't find variable: num
function hello() {
console.log('Hello!'); console.log(num); console.log(num); Come si vede, solo la
} // undefined // ReferenceError: dichiarazione viene
var num; Cannot access hoistata, non
Funziona ed è equivalente a num = 6; uninitialized variable. l’assegnazione
console.log(num); let num; Le dichiarazioni let
function hello() { // 6 num = 6; e const sono
console.log('Hello!'); console.log(num); hoistate ma non
} console.log(num); // 6 sono accessibili
// undefined
hello(); var num = 6;

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti
(anteprima)
Gli array sono specia
Gli oggetti in JavaScript sono collezioni arbitrarie di proprietà, più precisamente array li tipi di oggetti che
realizzano liste spars
associativi mutabili le cui chiavi rappresentano i nomi delle proprietà e e polimorfiche,
indicizzabili da 0
Es. let arr = [1
let descriptions = { ,2,3,,,’FooBar'
];
console.log(arr
work: "Went to work", [5]);
// ‘FooBar’
"touched tree": "Touched a tree"
};

I valori delle proprietà sono accessibili mediante la notazione obj.prop oppure obj['prop']
È possibile creare o rivalorizzare a posteriori una proprietà con l’operatore di assegnazione
Per eliminare una proprietà è sufficiente usare delete obj.prop

Gli oggetti possono possedere dei metodi, ovvero delle proprietà contenenti funzioni
A differenza di molti linguaggi orientati agli oggetti, in JavaScript non c’è distinzione fra funzione e metodo, a parte il fatto
che la funzione di un oggetto può usare la keyword this per riferirsi all’oggetto stesso
descriptions.change = function(s) { this.work = s };

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
funzionale
Alcuni aspetti di JavaScript si avvicinano a quelli della programmazione funzionale

La programmazione funzionale viene dall’idea che un programma possa essere considerato come una funzione matematica
La programmazione funzionale pura è estremamente forte nella sua formulazione e prevede che:
• Qualunque cosa all’interno del codice sia una funzione oppure un’espressione
• Non vi siano statement
• Non vi siano stati (variabili, oggetti, …)

for (var i = 0; i < anArr.length; i++) { newArr = anArr.map(function (val, ind) {


newArr[i] = anArr[i] * i; return val * ind;
} });

Programmazione imperativa Programmazione funzionale

JavaScript non implementa programmazione funzionale pura ma ne mutua alcune idee: funzioni come oggetti di prima
classe, chiusure, funzioni anonime, …

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
funzionale
Le funzioni in JavaScript sono oggetti di prima classe: le loro definizioni (in un binding o anonime) rappresentano “valori”
che possono essere passati come argomenti ad altre funzioni (callback), possono essere restituiti come valori da altre
funzioni e possono essere inseriti in qualunque struttura dati

Nota bene. Funzioni che accettano come argomenti altre funzioni sono dette funzioni di ordine superiore

Callback Più avanti scoprirem


o che le funzioni
sono oggetti di tipo
Function, e che
function callbackFunc() { console.log("timeout"); } quindi possiedono p
roprietà (es.
setTimeout(callbackFunc, 3 * 1000); name) e metodi (es.
call, la cui
chiamata equivale a
lla chiamata della
funzione stessa)
mailbox.sendMessage(message, () => console.log(“Messaggio inviato”));

Le funzioni di callback sono estremamente utilizzate nell’ambito della programmazione asincrona (funzioni “lente” che
chiamano altre funzioni quando terminano la loro esecuzione) e in generale nella programmazione event-driven

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
funzionale
Callback

array.forEach(callback [, thisArgument])

Ad esempio il metodo forEach di un array (chiariremo più avanti) prende come input una callback che esegue, elemento
per elemento, un’azione particolare

La callback da passare a forEach deve prevedere (al massimo) tre argomenti, ovvero l’elemento corrente, il suo indice e
l’intero array:

callback(item [, index [, array]])

Per esempio, potremmo scrivere:

function callback(e) {
console.log(e);
}

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.1

Si supponga di avere la seguente lista di oggetti e di volerla ordinare per cognome

const persone = [{nome: "Mario", cognome: "Rossi", professione: "impiegato"},


{nome: "Giuseppe", cognome: "Verdi", professione: "operaio"},
{nome: "Marco", cognome: "Neri", professione: "insegnante"}];

Si utilizzi la funzione sort che, se utilizzato con una callback con due argomenti a e b, ordina gli elementi comparandoli
coppia per coppia a e b (array.sort(compareFn) con compareFn(a, b))

In particolare, la callback deve restituire un valore positivo per posizionare b prima di a, un valore negativo per posizionare a
prima di b, 0 per mantenere l’ordine originale

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.2

Si supponga di voler riscrivere la funzione removeSong (espressa in maniera imperativa) utilizzando i principi della
programmazione funzionale

function removeSong(songsArr, songName) {


for (let i=0; i<songsArr.length; i++) {
const currSong = songsArr[i];
if (currSong.toLowerCase() === songName.toLowerCase()) {
console.log(currSong);
songsArr.splice(i, 1);
break;
}
}
}

Si utilizzi, in particolare, il metodo findIndex degli array

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Gli oggetti in JavaScript sono collezioni arbitrarie di proprietà, più precisamente array associativi mutabili le cui chiavi
rappresentano i nomi delle proprietà
Esiste la proprietà w
let descriptions = { ork in descriptio
ns?
Basta chiedere "wor
work: "Went to work", k" in descripti
ons
Quali sono tutte le p
"touched tree": "Touched a tree" roprietà di
descriptions? È
}; possibile ottenerle
mediante Object.k
eys(description
s)

I valori delle proprietà sono accessibili mediante la notazione obj.prop oppure obj['prop']
È possibile creare o rivalorizzare a posteriori una proprietà con l’operatore di assegnazione
Per eliminare una proprietà è sufficiente usare delete obj.prop

Gli oggetti possono possedere dei metodi, ovvero delle proprietà contenenti funzioni
A differenza di molti linguaggi orientati agli oggetti, in JavaScript non c’è distinzione fra funzione e metodo

descriptions.change = function(s) { this.work = s };

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
A proposito di this

Per funzioni (o metodi) dichiarate in maniera "classica", la parola chiave this viene associata all’oggetto su cui la funzione
viene invocata

let o = {
oldProp: 'this is an old property’
aMethod: function() {
this.newProp = "this is a new property";
console.log(‘Done’);
}
};

o.aMethod();

Non usare arrow function per definire un metodo, in quanto il this sarà associato all'oggetto globale

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Array

L’oggetto Array permette di collezionare una sequenza (non associativa) di valori

Si tratta di oggetti ridimensionabili e che possono contenere mix di differenti tipi di dati Dai un’occhiata alla
Gli elementi di un array sono indicizzabili a partire da 0 reference

let s = [element0, element1, /* ... ,*/ elementN]


let s = new Array(element0, element1, /* ... ,*/ elementN)

s.length; s.includes(‘Banana’); s.indexOf(element1); s.lastIndexOf(‘apple’);

s.push(‘Banana’); s.unshift(‘Element at first position’);


const removedLastItem = s.pop(); const removedFirstItem = s.shift();
const removedItems = s.splice(startIndex [, numElements]); // altrimenti, fino alla fine

const sCopy = s.slice(); const sFromOneToFour = s.slice(1, 4);

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Array

Come iterare su tutti gli elementi di un array?

const fruits = ['Apple', 'Mango', 'Cherry'];


for (const fruit of fruits) {
console.log(fruit);
}

Oppure ricordiamoci del metodo di ordine superiore forEach()

fruits.forEach(function(item, index, array) {


console.log(item, index);
});

Il metodo map() è simile a forEach() ma anziché effettuare operazioni per ogni elemento dell’array, ci permette di ottenere
un nuovo array i cui elementi sono il risultato della mappa applicata su ogni elemento dell’array originale
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Array

Più in generale, gli array dispongono di diversi metodi di ordine superiore

Oltre forEach() e map(), vi sono anche:


• filter(), per ottenere un nuovo array che contiene solo gli elementi che “passano” la funzione predicato
• reduce(), per combinare tutti gli elementi dell’array in un unico valore
• findIndex(), per trovare l’indice degli elementi che soddisfano una certa funzione predicato

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.3

Si scrivano le funzioni sum e range che permettano, rispettivamente, di calcolare la somma di una lista, e di costruire una
lista di numeri interi dati i due estremi (inclusi)

Ad esempio, sum(range(1, 10)) deve restituire 55

Esercizio 6.4

Si scriva una funzione che permetta di restituire il reverse di una lista


Si scriva, inoltre, una funzione che effettui il reverse in-place di una lista

Esercizio 6.5

Si scriva una funzione che permetta di effettuare la deep comparison fra due oggetti

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Oggetti vs dati primitivi

Gli oggetti appena introdotti si contrappongono ai tipi di dati primitivi, ovvero quelli introdotti finora (null, undefined,
stringhe, numeri, booleani) più i bigint e i Symbol

Immutabilità dei dati primitivi

Un dato primitivo non può cambiare


È possibile cambiare il valore a cui è associato un binding, ma non è il valore in
sé a cambiare

var bar = "baz";


console.log(bar); // baz
bar.toUpperCase();
console.log(bar); // baz
console.log(bar.toUpperCase()) // BAZ

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Mutabilità degli oggetti

Il contenuto di un oggetto può invece cambiare

let object1 = {value: 10};


let object2 = object1;
let object3 = {value: 10};

console.log(object1 == object2); // → true


console.log(object1 == object3); // → false
Attenzione. L’operato
re di confronto ===
fra dati
Nell’esempio, object1 e object2 puntano allo stesso “contenitore” primitivi confronta il
loro valore, mentre
non primitivi confro fra dati
Il loro contenuto, quando cambia, cambia insieme nta la loro identità,
ovvero se
sono lo stesso oggett
Anche dichiarando object1 o object2 con const, ne potremmo cambiare il o in memoria
Non esiste un comp
contenuto: il contenitore, infatti, non cambierebbe (non potremmo invece aratore “deep” built-
in in
JavaScript
effettuare una riassegnazione!)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Wrapper di valori primitivi

Eccetto undefined e null, tutti i valori primitivi in JavaScript hanno un oggetto equivalente che svolge il ruolo di wrapper
per quel valore primitivo
Vediamo un esempio per le stringhe, associate all’oggetto String

const string1 = "Una stringa primitiva";


const string2 = new String(“Un oggetto String”);

console.log(typeof string1) // string


console.log(typeof string2) // object

È possibile ottenere il valore primitivo di un oggetto wrapper mediante il metodo valueOf()

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Altri oggetti

Altri oggetti built-in in JavaScript includono le mappe


In generale, una mappa è una struttura dati che associa chiavi con valori
In JavaScript, l’oggetto Map permette di realizzare con precisione questo scopo, potendo avere come chiave qualunque
oggetto
let myMap = new Map();
myMap.set("nome", "Mario");
myMap.set(3.14, "Pi greco");
let myObj = {id: 123, data: "test"};
myMap.set(myObj, "Oggetto");

I Set sono invece insiemi di elementi senza duplicati


var mySet = new Set(); // oppure var mySet = new Set([1,2,1,2,2,”tre”);
mySet.add(1);
mySet.add(2);
mySet.add(“tre");
console.log(mySet.size); //3
console.log(mySet.has(2)); //true

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Prototipi

Se provassimo a creare un oggetto vuoto con let obj = {};, scopriremmo che esso possiede metodi come ad esempio
toString()
Ciò accade perché ogni oggetto in JavaScript deriva da un proprio prototipo da cui “eredita” tutte le proprietà e i metodi
In particolare, gli oggetti “semplici” sono realizzati mediante il prototipo di Object

Mettendo un po’ d’ordine, scopriamo che tutti gli oggetti che abbiamo considerato finora derivano da un loro prototipo

Array Boolean Date Function JSON Map

Math Number Object Promise RegExp Set String

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Prototipi

È possibile creare nuovi oggetti usando un altro oggetto come prototipo mediante il metodo Object.create()

let protoSpeaker = {
type: null,
speak(line) { Ricorda che è possib
ile anche definire un
console.log(`The ${this.type} speaker says '${line}'`); metodo con la segue
nte notazione
} let protoSpeake
r = {
}; speak: function
(line) {

}
let humanSpeaker = Object.create(protoSpeaker); }
humanSpeaker.type = 'human';
humanSpeaker.speak(‘Hello, World!');

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Prototipi

Questo sistema di prototipazione degli oggetti può essere informalmente associato al rapporto fra classi e istanze

A questo proposito potremmo pensare di realizzare una funzione che agisca come un costruttore, ovvero che ci restituisca un
oggetto a partire da un certo prototipo

Non useremo ques


function makeSpeaker(type) { ta strategia,
ci serve solo per cap
let speaker = Object.create(protoSpeaker); ire cosa
stiamo facendo!
speaker.type = type;
return speaker;
}

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Costruttori Attenzione!
Va distinto bene il m
odo in cui possiamo
Nella pratica, JavaScript fornisce una maniera semplice per la realizzazione di ottenere il prototipo
di un oggetto x con
prototipi e oggetti basati su di essi, che fa uso della funzione costruttore Object.getProto
typeOf(x), ed il
modo in cui un costr
function Speaker(type) { uttore è associato co
il prototipo degli ogg n
this.type = type; etti che può costruir
e,
ovvero mediante la p
this.speak = function (line) { roprietà prototype
Esempio: cosa resti
tuiscono
console.log(`The ${this.type} speaker says '${line}'`); Object.getProto
typeOf(Speaker)
}; e Speaker.protot
ype?
}

let humanSpeaker = new Speaker('human');

Utilizzando la keyword new davanti al nome di una funzione, quest’ultima sarà trattata come costruttore
Tutte le funzioni, infatti, ricevono una proprietà chiamata prototype che si può sovrascrivere o a cui si possono
aggiungere proprietà: questa proprietà contiene il prototipo degli oggetti che saranno costruiti mediante tale funzione se
usata come costruttore (tale prototipo viene anche inserito nella proprietà .__proto__ dei nuovi oggetti creati)
Nota bene. Per convenzione, i nomi dei costruttori iniziano con una lettera maiuscola
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Catena di prototipi

Come abbiamo detto, tutti gli oggetti derivano da un loro prototipo


Object.getPrototypeOf(Math.max) === Function.prototype

Ma da quale prototipo deriva il prototipo?


Supponiamo di aver creato un oggetto come const myDate = new Date()

Il suo prototipo (accessibile anche con la proprietà .__proto__) è Date.prototype


Ma Date.prototype, essendo un prototipo, è un oggetto e il suo prototipo sarà dunque Object.prototype
Il prototipo del prototipo di un oggetto base è null, che chiude la cosiddetta catena di prototipi

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Catena di prototipi ed ereditarietà prototipale

Supponiamo di avere un oggetto rabbit e poi di cambiarne il suo prototipo fissandolo a un altro oggetto animal

const animal = {
eats: true,
walk() {
alert("Animal walk");
}
};

const rabbit = {
jumps: true
Le proprietà e i metodi di animal saranno automaticamente disponibili in rabbit in
};
un meccanismo di ereditarietà prototipale
rabbit.__proto__ = animal; Se una proprietà non esiste in un oggetto, verrà cercata nel suo prototipo o nel
rabbit.walk(); prototipo del suo prototipo, risalendo la catena

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Catena di prototipi ed ereditarietà prototipale

Aggiungendo una proprietà ad un oggetto, che sia essa presente o meno nel prototipo, la proprietà viene
aggiunta al solo oggetto e “scollegata” (eventualmente) da quella del prototipo

const animal = {
eats: true,
walk() {
alert("I am walking!")
}
};

const rabbit = {
__proto__: animal
};
Questa tipologia di overriding può essere applicata per dare un
rabbit.walk = function() { comportamento “eccezionale” ad alcuni oggetti, benché
alert("Rabbit! Bounce-bounce!"); derivanti da un prototipo con comportamenti di default
};

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Classi in JS

Dal 2015, JavaScript è stato esteso con le classi ES2015, che si basano sempre sul concetto di prototipo, lo semplificano nella
notazione e lo estendono nelle potenzialità (metodi statici, metodi di istanza, ereditarietà, …)

class Speaker {
constructor(type) {
Sulla definizione dell
this.type = type; e classi
non viene effettuato
} l’hoisting
speak(line) {
console.log(`The ${this.type} speaker says '${line}'`);
}
}

let humanSpeaker = new Speaker('human');

Questa notazione ci permette di specificare un insieme di metodi, compreso il costruttore chiamato sempre constructor
Come le funzioni, anche le classi possono essere create all’interno di una espressione (let Speaker = class { … }), oppure
in maniera anonima (let humanSpeaker = new class { … }(…))
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Analizziamo il comportamento di this all’interno dei metodi di un oggetto, ovvero in una situazione in cui agisce
esattamente per come ci si aspetta

const point = {
x: 10,
y: 20
where() {
return `position (${this.x}, ${this.y})`
}
}

console.log(point.where());

Come ci aspettavamo, this.x e this.y corrispondono a 10 e 20

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Consideriamo adesso il seguente esempio

myMethods = {
where() {
return `position (${this.x}, ${this.y})`
},
origin() {
this.x = 0;
this.y = 0;
}
}

const point = {
x: 10,
y: 20,
where: myMethods.where
}

Cosa succede invocando myMethods.where() ?


E cosa invocando, adesso, point.where()?
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Quello che succede è che il contesto di esecuzione di una funzione contiene un riferimento al valore corrente di this, e
questo valore è dinamico per sua natura

function sayHello() { const mario = {


console.log(this.name + ' says hello'); name: 'Mario',
} helloFunction: bear.greeting
};
const bear = {
name: 'Ice Bear', mario.helloFunction();
hobbies: ['knitting', 'cooking', 'dancing'],
greeting: sayHello
};

bear.greeting();

Quando chiamiamo una funzione facendola precedere dal nome di un oggetto (es. bear.greeting()), stiamo facendo
un'operazione di binding implicito, ovvero stiamo implicitamente associando quel particolare oggetto al this del contesto
di esecuzione

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this Perché usiamo var
in questi esempi?
Perché le variabili dic
hiarate con var
nello scope globale
diventano
proprietà di window
e diventano
Controlliamo di aver capito bene... quindi accessibili tra
mite this.
quando avviene il bin
ding di default
function doSomething() { var a = 6;
console.log(this.a); const obj = {
} a : 12,
doSomething: function() { console.log(this.a) }
var a = 6; };

doSomething(); const foo = obj.doSomething;


foo();

Nel comportamento di default, ovvero quando siamo nel contesto di esecuzione globale, avviene il binding di default, cioè
this corrisponde all’oggetto globale (window nel browser e globalThis in Node)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Analizziamo quest'altro esempio

var input = 1;

function square1() {
setTimeout(function() { console.log(this.input * this.input) }, 1000);
};

function square2() {
console.log(this.input * this.input);
};

const obj = {
input : 3,
I due risultati saranno completamente diversi
square1: square1,
square2: square2 Ma perché? Probabilmente la funzione passata come callback viene
}; eseguita in un contesto di esecuzione diverso da quello che ci
aspettiamo
obj.square1();
obj.square2();

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Guardiamo ancora quest'esempio

const persona = { Nel primo caso otterremo il risultato che ci aspettiamo,


nome: "Mario", in quanto nomeCognome sarà chiamata in un contesto
cognome: "Rossi",
nomeCognome: function () { in cui this è associato all’oggetto persona
return this.nome + " " + this.cognome;
} Nel secondo caso, il risultato sarà Buongiorno
}; undefined undefined
console.log(persona.nomeCognome());
Il motivo è che la funzione nomeCognome, passata
function saluta(nomeCognome) { come callback, viene chiamata in un contesto di
console.log("Buongiorno " + nomeCognome());
esecuzione in cui this non è l’oggetto persona ma, in
}
questo caso, l'oggetto globale
saluta(persona.nomeCognome);

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Cosa succede, quindi, quando this viene utilizzato dall’esterno, ad esempio mediante una callback?

const persona = {
nome: "Mario",
cognome: "Rossi",
saluta: function() {
alert("Buongiorno " + this.nome + " " + this.cognome);
}
};

document.getElementById("pulsante").addEventListener("click", persona.saluta);

Chiaramente this verrà associato all’elemento con id pulsante della pagina web e non
all’oggetto persona, pertanto this.nome e this.cognome saranno undefined Metodi alternativi sono call() e
apply() che permettono di
Una soluzione a questo "problema" può essere il binding esplicito mediante il metodo bind chiamare direttamente un metodo
delle funzioni, con cui leghiamo il contesto di una funzione (this) a un certo oggetto fissato su un certo oggetto

persona.saluta = persona.saluta.bind(persona)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Ecco ancora un esempio

class Present {
constructor(containerElement) {
this.containerElement = containerElement;

// Create image and append to container.


this.image = document.createElement('img');
this.image.src = ‘path/to/giftbox/image';
this.image.addEventListener('click', this._openPresent);
this.containerElement.append(this.image);
}

_openPresent(event) {
this.image.src = 'path/to/gift/image';
this.image.removeEventListener('click', this._openPresent);
}
}

Quando cliccheremo sul pacco regalo, il valore di this sarà associato all’immagine e non all’oggetto di classe Present
Si veda l’Esempio 6.1

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
La soluzione per i problemi appena visti è quella di effettuare il binding fra l’istanza di una classe ed un metodo

class Present {
constructor(containerElement) {
Il metodo bind() consente di creare
this.containerElement = containerElement;
una nuova funzione in cui l’oggetto this è
this._openPresent = this._openPresent.bind(this); preimpostato
// Create image and append to container.
this.image = document.createElement('img');
this.image.src = 'path/to/giftbox/image'; Consiglio per il futuro!
this.image.addEventListener('click', this._openPresent); Non dimenticare di usa
this.containerElement.append(this.image); re il
metodo bind() all’inte
} rno
dei costruttori per asso
ciare
_openPresent(event) { gli event listener
this.image.src = 'path/to/gift/image';
this.image.removeEventListener('click', this._openPresent);
}
}

Si veda l’Esempio 6.1


Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.6

Utilizzare il metodo bind() per risolvere l’errore presente in questo codice

const persona = {
nome: "Mario",
cognome: "Rossi",
nomeCognome: function () {
return this.nome + " " + this.cognome;
}
};

function saluta(nomeCognome) {
console.log("Buongiorno " + nomeCognome());
}

saluta(persona.nomeCognome);

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.7

Si consideri di avere un vettore di oggetti della classe Present e che essi debbano comunicare all’app che li ha creati di
essere stati scartati

Mentre l’app possiede, chiaramente, un riferimento alle istanze dei pacchi regalo, ciascun oggetto non ha un riferimento
all’app che li ha creati

La soluzione, quindi, è quella di creare una callback che l’app passa alle istanze della classe Present
Si vuole, infatti, che quando tutti i pacchi regalo sono stati scartati, compaia un messaggio di avviso (ricorda che solo l’app
può sapere se tutti i pacchi sono stati scartati!)

Provare ora a sostituire le funzioni definite con arrow function


È ancora necessario il binding?

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Guardiamo questi due casi e cerchiamo di capire se esistono delle differenze

const myObject = { const myObject = {


a: 10, a: 10,
myMethod: function () { myMethod: () => {
console.log(this.a); console.log(this.a);
} }
}; };

myObject.myMethod() myObject.myMethod()

Le arrow function cambiano completamente le regole del gioco, in quanto effettuano una closure del this all'ambiente
lessicale genitore in cui viene definita
Nel caso a destra, lo scope in cui è definita la funzione myMethod di myObject è quello globale, pertanto this.a
corrisponderà window.a, che non esiste!

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Dal momento che le arrow function non hanno binding, è possibile trarre qualche conclusione generale

const person = { const person = {


name: 'Nathan', name: 'Nathan',
skills: ['HTML', 'CSS', 'JavaScript'], skills: ['HTML', 'CSS', 'JavaScript'],

showSkills() { showSkills() {
this.skills.forEach(function (skill) { this.skills.forEach(skill => {
console.log(`${this.name} is skilled in ${skill}`); console.log(`${this.name} is skilled in ${skill}`);
}); });
}, },
}; };

person.showSkills(); person.showSkills();

& La callback sarà eseguita in un contesto in cui this non è person ✅ Usa le arrow function come callback o handler "inline"
& Evitare le funzioni classiche come callback ✅ Non c'è necessità di fare binding
& Altrimenti, ricordare di effettuare il binding

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
A proposito
di this
Dal momento che le arrow function non hanno binding, è possibile trarre qualche conclusione generale

const person = { const person = {


name: 'Nathan', name: 'Nathan',
skills: ['HTML', 'CSS', 'JavaScript'], skills: ['HTML', 'CSS', 'JavaScript'],

showSkills: () => { showSkills() {


this.skills.forEach(skill => { this.skills.forEach(skill => {
console.log(`${this.name} is skilled in ${skill}`); console.log(`${this.name} is skilled in ${skill}`);
}); });
}, },
}; };

person.showSkills(); person.showSkills();

& Non usare le arrow function come metodi di un oggetto in quanto il this ✅ Usa le funzioni classiche come metodi, specie se questi devono essere
sarà associato all'ambiente genitore chiamati mediante binding implicito

E se usassi una arrow function come metodo all'interno di una funzione costruttore?

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
In un modello di programmazione sincrona, le cose accadono una alla volta
Quando chiamiamo una funzione la cui esecuzione richiede del tempo, il programma si ferma per tutto il tempo di esecuzione
della funzione

Un modello di programmazione
asincrona permette alle cose di
avvenire contemporaneamente
Quando un’azione viene iniziata, il
programma continua ad essere
eseguito e sarà poi informato della Nel grafico, le linee più spesse
rappresentano l’esecuzione del
fine dell’esecuzione dell’azione
programma del tempo, mentre le
A destra è schematizzato un linee sottili rappresentano il
programma che richiede due risorse tempo speso nell’attesa delle
dalla rete (azione lenta) e ne deve risorse dalla rete

combinare il risultato

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
In generale, la maggior parte dei programmi JavaScript saranno scritti in maniera tale che alcune porzioni di codice saranno
eseguite ora ed altre saranno eseguite più tardi

Tuttavia, più tardi non significa necessariamente subito dopo ora

// ajax(..) è una funzione Ajax arbitraria asincrona data da una libreria


var data = ajax( "http://some.url.1" );

console.log(data);
// Oops! `data` non avrà i risultati Ajax, questa è un’azione che vogliamo effettuare più tardi

// ajax(..) is some arbitrary Ajax function given by a library


ajax( "http://some.url.1", function myCallbackFunction(data){

console.log( data ); // Yay, I gots me some `data`!

} );
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Proviamo a capire qualcosa in più sulla programmazione asincrona, partendo dal funzionamento base di JavaScript
JavaScript è un linguaggio di programmazione single-threaded, con un unico call stack
Quindi JavaScript può fare solo una cosa alla volta function multiply(a, b) {
multiply(n, n) return a * b;
}
square(n) square(n)
function square(n) {
printSquare(4) printSquare(4) printSquare(4) return multiply(n, n);
}
main() main() main() main()
function printSquare(n) {
const squared = square(n);
console.log(squared);
}
square(n) console.log(squared)
printSquare(4);
printSquare(4) printSquare(4) printSquare(4) printSquare(4)

main() main() main() main() main()

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Cosa succede utilizzando un unico call stack in maniera sincrona?

let foo = $.getSync(‘http://foo.com');


let bar = $.getSync(‘http://bar.com');
let qux = $.getSync(‘http://qux.com');

$.getSync(…) $.getSync(…) $.getSync(…)

main() main() main() main() main() main() main()

Tutte queste funzioni hanno un comportamento bloccante sul programma


Se eseguite in un browser, finché non sono terminate non è possibile eseguire alcuna altra azione JS

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
La soluzione è quella della programmazione asincrona che utilizza funzioni non bloccanti
Una possibile strategia è quella di chiamare una funzione “lenta” e passarle una callback che venga eseguita al termine
In generale, la maggior parte dei programmi JavaScript saranno scritti in maniera tale che alcune porzioni di codice saranno
eseguite ora ed altre saranno eseguite più tardi
console.log('Hi!');

setTimeout(function() { console.log(‘Hi!’);
console.log('There');
}, 5000); main() main() main()

console.log('Poliba');

setTimeout(…) console.log(‘Poliba’);

main() main() main() main() console.log(‘There’);

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Ma se il runtime di JavaScript esegue una sola azione alla volta, com’è possibile che possa in maniera concorrente scaricare
delle risorse dal web oppure contare i secondi che passano?

Ricordiamo che il codice JavaScript viene eseguito


all’interno di un browser oppure in Node.js, ovvero in un
hosting environment

Il browser è esso stesso multi-threaded e multi-process e


mette a disposizione nella sua implementazione delle
WebAPI a cui poter fare delle chiamate

Ad esempio, vi sono API per la gestione del timeout, oppure


per fare richieste HTTP

Node.js, alternativamente, implementa specifiche API scritte


in C++

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona console.log(‘Hi!’);

setTimeout(function() {
console.log(‘There’);
}, 5000);
WebAPIs
console.log(‘Poliba’);
timer( ) callback
A sinistra è mostrato il funzionamento
chiama
inserisce dell’event loop con cui opera il runtime di
subito JavaScript nel browser
al termine

Quando la chiamata di una API termina,


l’eventuale callback viene inserita nella
callback coda dei task

Se lo stack è vuoto, il
Cosa succederebb
il primo elemento viene e con un
primo elemento timeout di 0 secon
spostato nello stack di?
della coda dei task
se questo è vuoto
viene eseguito

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Allo stesso modo funziona una richiesta
AJAX asincrona
WebAPIs
(L’esempio seguente utilizza jQuery per semplicità
XHR( ) callback
di scrittura)
console.log(‘Hi!’);
chiama inserisce
subito al termine $.get(‘url’, function cb(data) {
console.log(data)
});
$.get(…)
console.log(‘Istruzione successiva’);
callback

Prova a speriment
are
il primo elemento viene l’event loop con qu
esto
spostato nello stack tool di visualizzaz
ione!
se questo è vuoto

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione La coda dei microtask contiene le callback delle

asincrona
operazioni considerate più urgenti o importanti, e
saranno eseguite prima
La coda dei macrotask contiene le callback delle
operazioni meno urgenti

WebAPIs

XHR( ) callback

chiama inserisce
subito al termine

$.get(…)

callback

il primo elemento viene


spostato nello stack
se questo è vuoto

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Non conosciamo la libreria jQuery,
Il supporto alla programmazione asincrona offerto da JavaScript permette di eseguire
ma ricorda che qui non è
attività “in background” che non interferiscono con il flusso di elaborazione principale
necessario focalizzarsi sulla
Il modello di programmazione asincrona visto finora è basato sull’utilizzo di callback funzione specifica che stiamo
utilizzando per l’esempio, quanto
Partiamo dall’esempio seguente, che effettua una richiesta AJAX utilizzando jQuery invece sui concetti della
programmazione asincrona
$.get(“/users”, { id: "12345" }, function(user) {
$("#resultMessage").html("Nome utente: " + user.Name);
});

Il terzo argomento di $.get() è la funzione di callback che sarà invocata quando l’oggetto JSON relativo all’utente richiesto
sarà stato ricevuto

Supponiamo, una volta ricevuto l’oggetto utente (che contiene l’ID del suo blog) di voler mostrare i post del suo blog

$.get("/users", {id: “12345"}, function(user) {


$.get("/blogs", {id: user.blogId}, function(blog) {
displayPostList(blog.posts);
});
});
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Vogliamo ora ottenere anche le fotografie contenute nell’album personale dell’utente

$.get("/users", {id: “12345"}, function(user) {


$.get("/blogs", {id: user.blogId}, function(blog) {
displayPostList(blog.posts);
});
$.get("/photos", {id: user.albumId}, function(album) {
displayPhotoList(album.photos);
});
});

Abbiamo dovuto costruire la callback passata alla prima richiesta come una funzione che fa due richieste, a loro volta ciascuna
con una propria callback

Viene legittimo chiedersi se displayPostList e displayPhotoList saranno effettivamente chiamate una dopo l’altra
Effettivamente non è così, in quanto l’ordine della loro esecuzione sarà determinato dal termine delle relative funzioni chiamanti

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione Il fenomeno di codice

asincrona
estremamente annidato che si
viene a verificare è detto
function doStep1(init, callback) { callback hell oppure pyramid
const result = init + 1;
Supponiamo di trasformare un normale callback(result);
of doom, a causa
codice sincrono utilizzando le callback } dell’indentazione
function doStep2(init, callback) {
function doStep1(init) { const result = init + 2;
return init + 1; callback(result); Questo fenomeno è
} } estremamente frequente con
function doStep2(init) { function doStep3(init, callback) { l’utilizzo di callback nella
return init + 2; const result = init + 3;
} callback(result); programmazione asincrona
}
function doStep3(init) {
return init + 3; function doOperation() { Il debug del codice diventa
} doStep1(0, result1 => {
doStep2(result1, result2 => { estremamente difficile, così
function doOperation() { doStep3(result2, result3 => {
console.log(`result: ${result3}`);
come la gestione degli errori
let result = 0;
result = doStep1(result); });
result = doStep2(result); });
result = doStep3(result); }); Per questo motivo nella
console.log(`result: ${result}`);
} } programmazione asincrona
moderna in JS si adotta il
doOperation(); doOperation();
promise pattern

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Il promise pattern è la fondazione della programmazione asincrona moderna in JavaScript

Questo pattern parte dall’assunto di avere a disposizione degli oggetti che possano rappresentare il valore pendente di una
operazione asincrona
Tale oggetto può essere utilizzato, all’interno del codice, per stabilire le attività da eseguire al termine dell’operazione
asincrona

Nel promise pattern, una funzione asincrona restituisce una promise, ovvero un oggetto che rappresenta, appunto, l’esito
della sua esecuzione

In un certo senso, anziché incorporare la continuazione di un programma asincrono all’interno di una callback, il promise
pattern permette di ottenere un oggetto che rappresenta la capacità di sapere quando un task asincrono è finito e
decidere quindi cosa fare dopo

Il promise pattern è stato integrato in maniera nativa in JS con ECMAScript 2015


Precedentemente, era necessario fare uso di librerie esterne come Q

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Un’analogia reale è quella del fast-food: vado in cassa, ordino un cheeseburger e do al cassiere 1,99€
Ho, di fatto, creato una richiesta di un “valore di ritorno” (il mio cheeseburger)

Ovviamente, il cheeseburger non è immediatamente pronto, quindi il cassiere mi da qualcosa al posto del cheeseburger: una ricevuta
con un numero d’ordine
La ricevuta è una promessa del tipo “ti devo qualcosa”, in questo caso un cheeseburger

La ricevuta fra le mani rappresenta il mio cheeseburger futuro e non mi devo più preoccupare, perché qualcuno lo sta preparando per
noi
Nel frattempo posso fare altro, come inviare un messaggio a un amico e dirgli “Ehi! Vieni a pranzo con me? Sto per mangiare un
cheeseburger”
Non ho ancora il cheeseburger fra le mani, ma posso già ragionare su di esso perché ho un numero d’ordine che lo “sostituisce”
slegandolo dal concetto di tempo e mi assicura di avere un valore futuro

Finalmente sento “Ordine 113!”: il mio valore futuro è pronto!


Vado in cassa e scambio la ricevuta con il cheeseburger, ovvero scambio la promessa di un valore col valore stesso
Potrebbe anche succedere che, una volta in cassa, mi dicano che i cheeseburger sono finiti: in questo caso il mio valore di ritorno sarà,
purtroppo, un fallimento

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Stati di una promise

Una promise può trovarsi in uno dei seguenti stati

• Resolved: una promise è risolta quando il valore che rappresenta diviene disponibile, Pending
cioè quando l'attività asincrona restituisce un valore
• Rejected: Una promise è rigettata quando l'attività asincrona associata non
restituisce un valore o perché si è verificata un'eccezione oppure perché il valore Resolved Rejected
restituito non è considerato valido
• Pending. Una promise è pendente quando non è né risolta né rigettata, cioè la
richiesta di esecuzione di un'attività asincrona è partita ma non abbiamo ancora
ricevuto un risultato

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Un toy example non reale

Il promise function httpGetAsync(url) {


return new Promise(function(resolve, reject) {

pattern const response = httpGetSync(url);


if (response.ok) resolve(response.body)
else reject(response.error)
});
}
Creazione di una promise

let promise =
Un esempio funzionante...
new Promise(function(resolve, reject) { function httpGet(url) {
if (condizione) { return new Promise(function(resolve, reject) {
resolve(valore); var httpReq = new XMLHttpRequest();
httpReq.onreadystatechange = function() {
} else { var data;
reject(motivo); if (httpReq.readyState == 4) {
} if (httpReq.status == 200) {
data = JSON.parse(httpReq.responseText);
}); resolve(data);
} else {
Il costruttore dell’oggetto Promise prevede un
reject(new Error(httpReq.statusText));
parametro che rappresenta il promise handler }
Il promise handler è una funzione che viene invocata }
};
immediatamente e che riceve, a sua volta, due httpReq.open("GET", url, true);
funzioni da invocare rispettivamente per risolvere la httpReq.send();
});
promise a un valore o per rigettarla, in base a }
determinate condizioni
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Uso di promise

Il vantaggio dell’uso di promise è quello di semplificare l’uso di funzioni asincrone evitando di passare attraverso le callback e
potendo immaginare, invece, di disporre immediatamente del risultato (trattasi, in realtà, del valore futuro)

Per lavorare sul risultato di una promise è possibile utilizzare il metodo then
In particolare, then prevede due argomenti: il primo è un resolve handler che sarà eseguito nel caso di promise risolta, il
secondo (reject handler, opzionale) è una funzione che sarà eseguita nel caso di promise rigettata

httpGet(‘url/to/fetch‘)
.then(value => console.log(‘Tutto ok: ‘ + value),
error => console.log(‘Si è verificato un errore’));

In particolare, then restituisce un’altra promise, che risolve al valore che la handler function restituisce oppure, se
quest’ultima restituisce una promise, aspetta quella promise e poi risolve al suo valore

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Torniamo all’esempio del blog dell’utente: dovevamo necessariamente fare due richieste HTTP una di seguito all’altra
Con la nostra nuova funzione httpGet che ci restituisce una promise l’idea potrebbe essere la seguente
httpGet("/utente/12345") In questo tipo di scrittura, la funzione che gestisce la
.then(function(utente) {
risoluzione della prima promise è una nuova chiamata
httpGet("/blog/" + utente.blogId)
.then(function(blog) { ad httpGet
displayPostList(blog.posts);
});
});

Ma questo non è di nuovo il callback hell dovuto all’annidamento delle callback? Sì, lo è
Ma ricordiamoci che il metodo then su una promise ci restituisce una nuova promise… e allora?

httpGet("/utente/12345")
.then(utente => httpGet("/blog/" + utente.blogId))
.then(blog => displayPostList(blog.posts));

Questo approccio è chiamato promise chaining, in quanto realizza, appunto, una catena di promise

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Gestione degli errori

Come detto, il metodo then accetta un resolve handler, mentre il metodo catch accetta un reject handler

httpGet("/utente/12345”)
.then(utente => httpGet("/blog/" + utente.blogId)).catch(e => console.log(e));

Mediante il metodo then è comunque possibile passare sia il resolve handler che il reject handler

httpGet("/utente/12345")
.then(utente => httpGet("/blog/" + utente.blogId), e => console.log(e))

La rejection di una promise viene propagata alla promise generata dal then, che viene quindi a sua volta rigettata
Pertanto, se una promise all’interno di una catena fallisce, l’output dell’intera catena sarà marcato come rigettato e nessun
handler di successo sarà invocato da quel punto in poi
È possibile, quindi, pensare di posizionare un solo metodo catch in fondo alla catena

httpGet("/utente/12345")
.then(utente => httpGet("/blog/" + utente.blogId))
.then(blog => displayPostList(blog.posts)).catch(e => console.log(e));
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.8

Realizzare, mediante il promise pattern, una funzione alarm che realizzi una funzione asincrona di sveglia, che dopo un
tempo preimpostato esegua un’azione come un messaggio

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Combinazione di promise

Il metodo then utilizzato finora permette di stabilire un ordine fra le funzioni asincrone
A volte è necessario ottenere i valori di diverse promise senza che queste dipendano necessariamente l’una dall’altra

A questo proposito è possibile utilizzare il metodo Promise.all(), la cui promise è risolta solo quando tutte le promise
all’interno dell’array passatogli come argomento sono risolte (rigettata anche quando una sola è rigettata)

const fetchPromise1 = httpGet(‘url1’), fetchPromise2 = httpGet(‘url2’), fetchPromise3 =


httpGet(‘url3’);

Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])


.then( responses => {
for (const response of responses) {
console.log(`${response.url}: ${response.status}`);
}
})
.catch( error => {
console.error(`Failed to fetch: ${error}`)
});
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
async/await

L'introduzione delle promise in JavaScript ha consentito di semplificare notevolmente la struttura del codice asincrono
rispetto all'approccio basato sull'utilizzo di callback
La coppia di parole async/await permettono di lavorare con le promise, semplificando la sintassi del codice asincrono e
realizzando una struttura tipica del codice sincrono
La parola chiave async permette di dichiarare una funzione come asincrona
La parola chiave await (utilizzabile solo all'interno di funzioni asincrone) permette di sospendere un’esecuzione in attesa
che la promise associata ad un’attività asincrona venga risolta o rigettata
function getUtente(userId) { async function getUtente(userId) {
httpGet(“/utente/" + userId) try {
.then(response => { let response = await httpGet(“/utente/" + userId);
console.log(response); console.log(response);
}).catch(error => } catch (e) {
console.log(“Errore!" console.log("Si è verificato un errore!");
)); }
} }
All’interno di funzioni async è possibile lavorare come se await permettesse di ottenere valori già risolti
Le funzioni async continuano a restituire all’esterno delle promise
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.8 (continua)

Relizzare nuovamente quanto richiesto nell'Esercizio 6.8 utilizzando, questa volta, la sintassi async/await

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Modularità
in JavaScript
Per ragioni pratiche di riuso e leggibilità, programmi complessi sono spesso organizzati in file chiamati moduli
Ciascun modulo specifica quali moduli utilizza e quali interfacce espone
Le relazioni fra moduli sono dette di dependency

Uno o più moduli possono comporre un package, ovvero parti di codice che si possono distribuire e installare (per JavaScript, si
veda l’infrastruttura fornita da NPM)
Un package, a sua volta, può dipendere da altri package
Quando un package viene aggiornato (bug fixing o nuove funzionalità), tutti i programmi che lo usano possono aggiornarlo alla
nuova versione (senza dover correggere codice manualmente)

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Modularità
in JavaScript
CommonJS

Progetto proposto nel 2009 per le necessità di modularità nell’uso di JavaScript fuori dal browser
Attualmente implementato in molti scenari, specialmente server-side, come Node.js

Aggiungendo una proprietà o un metodo all’oggetto exports (o module.exports), è possibile creare un’interfaccia

exports.printLine = function(line) {
console.log(line);
}

Chiamando la funzione require, è possibile importare una dependency per utilizzare le sue interfacce
const utils = require(‘./utils’); // oppure require(‘package-name’);
utils.printLine(‘esempio’);

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Modularità
in JavaScript È possibile specificare un
alias per un
oggetto importato (ad es.
import
ordinal as od from
Moduli ECMAScript “ordinal”)
È possibile utilizzare impo
rt * as
Ordinal from “ordin
Nel 2015 ECMA ha introdotto il proprio sistema di moduli al” per
importare tutte le interfac
La parola chiave import permette di importare oggetti da moduli ce e accedervi
mediante dot notation
import ordinal from "ordinal";
import {days, months} from "date-names";

La parola chiave export permette di rendere disponibili function prodotto(x, y) => x * y;


delle interfacce verso l’esterno function somma(x, y) => x + y;
function prodotto(x, y) => x * y; const pigreco = 3.141592;
export function somma(x, y) => x + y; class Circonferenza {
export const pigreco = 3.141592; constructor(raggio) {
export class Circonferenza { this.raggio = raggio;
constructor(raggio) { }
this.raggio = raggio; }
} export {somma, pigreco, Circonferenza}
}

Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)

Potrebbero piacerti anche