06 Javascript
06 Javascript
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
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
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)
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
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".
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)
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
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
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;
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
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Funzioni Una funzione è detta
pura se
È 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
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
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();
}
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
}
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)
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, …)
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
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:
function callback(e) {
console.log(e);
}
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Esercizi
Esercizio 6.1
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
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
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
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
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Array
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
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)
Esercizio 6.4
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
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Mutabilità degli oggetti
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
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Oggetti,
prototipi e classi
Altri oggetti
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
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
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?
}
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
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}'`);
}
}
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());
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
}
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; };
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
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;
_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);
}
}
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!)
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
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
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
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
console.log(data);
// Oops! `data` non avrà i risultati Ajax, questa è un’azione che vogliamo effettuare più tardi
} );
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)
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Programmazione
asincrona
Cosa succede utilizzando un unico call stack in maniera sincrona?
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’);
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?
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
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
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
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
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
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)
Il promise
pattern
Stati di una promise
• 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
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)
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";
Antonio Ferrara | Fondamenti del Web (Ingegneria del Software e Fondamenti Web)