Roma @ IED
10 Aprile 2015
Essendo lo scopo dei nomi di variabile la leggibilita', e' opportuno seguire o darsi delle regole per l'assegnazione dei nomi:
Ad esempio per convenzione si utilizza il camelCase per i nomi composti.
E' bene sapere che alcuni nomi in JavaScript sono riservati alla sintassi (presente e in alcuni casi futura) e non possono essere utilizzati come nomi di variabile:
break case catch class const continue debugger
default delete do else enum export extends false
finally for function if implements import in
instanceof interface let new null package private
protected public return static super switch this
throw true try typeof var void while with yield
Non e' necessario ricordare tutte le parole riservate ma conviene sapere che il loro uso potrebbe generare errori e malfunzionamenti.
Alcuni nomi, come undefined o NaN sono variabili read-only predefinite nello spazio globale.
Questo vuol dire che in un contesto diverso da quello globale (e.g. in un funzione), potrebbero essere ridefinite.
NON FATELO MAI
function testRedefineUndefined() {
var undefined = 5;
console.log("TEST", undefined == 5);
}
Oltre ai classici operatori matematici (+, -, *, /), due operatori molto comuni applicati sui tipi di dato numerici sono gli incrementi/decrementi:
> var count = 10;
> var unitPrice = 29.99;
> var appliedVAT = 0.22;
> var totalPrice = count * unitPrice
> var total = totalPrice +
(totalPrice * appliedVAT)
> var count = 0;
> count++
0
> count
1
> count += 10
11
> --count
10
> count
10
Abbiamo gia' visto come l'operatore "+" applicato alle stringhe ci consenta di comporle, a questo punto avremo bisogno di un modo per ricavare la lunghezza di una stringa:
> var welcomePrefixStr = "Hello, ";
> welcomePrefixStr.lenght
7
> (welcomPrefixStr + "Luca" + "!!!").length
14
length e' un attributo degli oggetti di tipo String
Potremo inoltre accedere ai singoli caratteri della stringa:
> var welcomeStr = "Hello World!!!";
> welcomeStr[0]
"H"
> welcomeStr[welcomeStr.length - 1]
"!"
e tagliare la stringa (slicing):
> welcomeStr.slice(0,5);
"Hello"
> welcomeStr.slice(6);
"World!!!"
I tipi boolean e le operazioni tra booleani sono alla base dell'utilizzo delle strutture di controllo.
Per struttura di controllo intendiamo particolari costrutti (in genere parte della sintassi) del linguaggio di programmazione con i quali e' possibile controllare il flusso delle istruzioni da eseguire.
Ad esempio e' possibile condizionare l'esecuzione di un blocco di istruzioni mediante la struttura di controllo if / else:
var isLoggedIn = true,
userMsg = "Not logged";
if (isLoggedIn) {
userMsg = "Welcome";
}
if (condition) {
__// codice da eseguire quando
// la condizione si verifica
} else {
__// codice da eseguire quando
// la condizione NON si verifica
}
Una importante classe di operazioni che hanno come risultato un valore booleano, sono gli operatori di comparazione: ">", "<", ">=", "<=", "==" e "==="
if (total > userAvailableCredits) {
errorMessage = "Non hai abbastanza crediti";
}
Gli operatori di comparazione sono spesso utilizzati nei cicli, strutture di controllo che consentono di ripetere un blocco di istruzioni, come il while:
var i = 0, total = 0;
while (i < 10) {
total += i;
i++;
}
while e' una struttura di controllo che ci consente di ripetere un blocco di istruzioni fino a quando la condizione tra parentesi tonde e' true
I tipi di dato primitivi vengono spesso organizzati all'interno di strutture dati composte come gli Array e Object.
L'Array e' una struttura dati in cui i valori sono indicizzati da un numero
(lista di valori)
Un Oggetto e' una struttura dati in cui i valori sono indicizzati da una stringa
(mappa chiave-valore)
Un Array puo' essere creato mediante la sua sintassi literal, in cui gli elementi (se ce ne sono) sono racchiusi tra parentesi quadre e separati da virgole:
var users = [];
var roles = [ "admin", "user", "guest" ];
Per accedere ad un singolo elemento di un Array si utilizza la stessa sintassi utilizzata per accedere ad un carattere di una stringa (ed in fondo una stringa e' molto simile ad un array di caratteri)
> var users = [ "admin", "user", "guest" ]
> users[0]
"admin"
> users[ users.length - 1 ]
"guest"
cosi' come per reperire l'attuale lunghezza dell'array utilizzeremo la proprieta' length
E' possibile utilizzare la stessa sintassi per sostituire l'elemento che si trova ad una determinata posizione:
> var postTags = ["webapps", "nodejs"]
> postTags[1] = "angularjs"
> postTabs
["webapps", "angularjs"]
Se assegniamo un valore ad un indice superiore alla sua attuale lunghezza, l'array sara' allargato fino al nuovo indice e riempito di elementi undefined:
> postTabs[10] = "ionic"
> postTabs
["webapps", "angularjs", undefined x 8, "ionic"]
> postTabs.length
11
In JavaScript (a differenza dei linguaggi di programmazione piu' statici) e' possibile creare degli array in cui i tipi di dati non siano uniformi:
> var mixedLst = [ 3, "Lorem", true ];
> mixedLst[0]
3
> mixedLst[1]
"Lorem"
> mixedLst[2]
true
Una matrice non e' altro che un array multidimensionale, quindi sara' rappresentabile come un array contenuto in un array (tante volte quanto le dimensioni dell'array)
var rolePermissionsMatrix = [
[ "manageUsers", "managePosts" ],
[ "managePosts" ],
[ "readPosts" ]
];
> rolePermissionMatrix[0][1]
"managePosts"
Possiamo scorrere gli array utilizzando la struttura di controllo while
var prices = [ 10, 9.9, 15 ];
var total = 0;
var i = 0;
while (i < prices.length) {
total += prices[i++];
}
o ancora meglio possiamo utilizzare un ciclo for
var prices = [ 10, 9.9, 15 ];
var total = 0;
for (var i = 0; i < prices.length; i++) {
total += prices[i];
}
Gli oggetti di tipo array sono dotati di proprieta' come length e alcuni metodi di utilita', come slice e concat, push e pop, shift e unshift, sort e reverse, che ne facilitano le manipolazioni piu' comuni.
Ad esempio con unshift e push possiamo aggiungere elementi rispettivamente in testa e in coda all'array:
> var users = []
> users.push("user1");
1
> users.push("guest");
2
> users.unshift("admin");
3
> users
["admin", "user1", "guest"]
Mentre al contrario con shift e pop possiamo rimuovere elementi rispettivamente dalla testa e dalla coda dell'array:
> users
["admin", "user1", "guest"]
> users.pop()
"guest"
> users.shift()
"admin"
Un altro metodo molto utile e' concat con la quale possiamo concatenare uno o piu' array:
> [1,2,3].concat(["p1", "p2"], [true, false])
[1, 2, 3, "p1", "p2", true, false]
Mentre con slice possiamo estrarre delle sotto-sequenze dell'array (come per le stringhe):
> [1, 2, 3].slice(0,1)
[1, 2]
> [1, 2, 3].slice(2)
[3]
> [1, 2, 3].slice(-1)
[3]
Mediante il metodo indexOf possiamo ricavare l'indice di un elemento (o -1 se l'elemento non e' presente nell'array):
> ["admin", "guest"].indexOf("guest")
1
> ["admin", "guest"].indexOf("unknown")
-1
Anche gli Object possono essere creati mediante una comoda sintassi literal:
> var routeDestinations = {};
> var user = { "login": "admin",
"email": "admin@me.me" };
L'oggetto e' una struttura dati composta di tipo chiave-valore, in cui la chiave dovra' essere di tipo stringa:
var data = {
__loggedUser: "admin",
"chiave con spazi": { }
}
Per accedere ai valori contenuti nell'oggetto attraverso una sintassi molto simile a quella utilizzata con gli Array:
> var usersEmailMap = { "admin": "admin@me.me", ... };
> usersEmailMap["admin"]
"admin@me.me"
Se la chiave dell'oggetto e' un identificativo valido possiamo utilizzare una sintassi alternativa:
> usersEmailMap.admin
"admin@me.me"
Nota: - la sintassi in formato array consente di utilizzare una variabile come chiave, quindi e' utile per accedere ad una diversa proprieta' a runtime, mentre la sintassi con il "." rende l'intenzione staticamente presente nei sorgenti (a seconda dello scopo si utilizzera' l'uno piuttosto che l'altra tecnica)
Analogamente agli array, possiamo utilizzare la sintassi di selezione di un elemento per riassegnarne il valore associato:
> usersEmailMap["admin"] = "new.admin@me.me"
> usersEmailMap.user01 = "user01@me.me"
Per eliminare una proprieta' da un Oggetto si utilizza l'operatore delete:
> delete usersEmailMap["user01"]
> usersEmailMap["user01"]
undefined
Un Oggetto non ha una proprieta' length con il quale poterne valutare la grandezza, ma e' possibile ricavare l'array delle chiavi definite, mediante il metodo di utiliza' Object.keys:
> Object.keys(usersEmailMap)
["admin"]
> usersEmailMap["user01"] = undefined
> Object.keys(usersEmailMap)
["admin", "user01"]
I valori assegnati alle proprieta' di un oggetto possono essere tipi di dato primitivi (e.g. numeri, stringhe e booleani), ma possono anche essere altri oggetti o altri array, consentendoci di creare strutture dati piu' complesse.
var user = {
login: admin,
roles: ["manage_users, manage_posts"],
contacts: {
email: "admin@me.me"
tel: "02020202"
}
}
> user.roles[1]
"manage_posts"
> user["contacts"]["tel"]
"02020202"
Quando il valore di una proprieta' di un oggetto e' una funzione, la proprieta' viene detta metodo, ad esempio negli Array abbiamo gia' visto il metodo indexOf:
> ["admin", "user01"].indexOf("user01")
1
Anche negli oggetti creati da noi potremo definire dei metodi:
var userList = {
users: [ ... ],
printUsers: function() {
console.log("USER LIST", this.users);
}
}
> userList.printUsers()
"USER LIST" [ ... ]
All'interno di un metodo, this rappresenta l'oggetto di cui il metodo fa parte,
Le variabili JavaScript definite con var avranno:
// questa variabile si trova nello spazio globale
var total = 0;
function sum(a, b) {
// visibile solo nella funzione sum
var total = a + b;
return total;
}
Se all'interno di una funzione dimentichiamo di dichiarare una variabile prima del suo utilizzo, la variabile verra' definita nello spazio globale
var total = 0;
function test() { total = 5; }
test()
console.log("TOTAL", total);
Per isolare le variabili globali all'interno di un contesto non globale in modo da minimizzare i rischi di collisioni dei nomi si utilizz spesso il seguente pattern:
(function () {
var moduleName = "This is module A";
window.testModuleA = function() {
console.log("MODULE A", moduleName);
}
})()
(function () {
var moduleName = "This is module B";
window.testModuleB = function() {
console.log("MODULE B", moduleName);
}
})()
Lo standard JavaScript prevede la presenza di uno spazio globale.
Quando l'interprete e' integrato all'interno di un browser lo spazio globale corrisponde all'oggetto window.
L'oggetto window appartiene alla cosiddetta DOM API (Document Object Model), l'API che consente all'interprete JavaScript di integrare le funzionalita' fornite dal browser Web, in particolare window rappresenta il tab o la window contenente il documento:
> window.location
Location { ..., origin: "http://localhost:9999", ...}
> window.prompt
function prompt() { [native code] }
L'oggetto console (inizialmente definito da developer tools come Firebug), viene utilizzato per emettere dei log utili durante lo sviluppo, e fa ora parte dello spazio globale:
> window.console
Console { ..., log: function }
> console.log("TRACE LOG", { test: 123 })
TRACE LOG Object {test: 123}
L'oggetto document rappresenta il documento HTML. Tramite l'API esposta da document potremo selezionare, aggiungere, modificare l'albero degli elementi della pagina:
> document.body
<body>...</body>
> document.body.innerHTML
"<div>
...
</div>"
Nel tempo sono stati aggiunti metodi sempre piu' avanzati per selezionare e navigare gli elementi contenuti nella pagina.
Il piu' recente e' querySelector / querySelectorAll che consente di selezionare gli elementi mediante la sintassi dei selettori CSS.
> document.querySelector("section")
<section>...</section>
> document.querySelectorAll("section")
[<section>...</section>,
...
<section>...</section>]
> document.querySelectorAll("section.title")
...
Una delle caratteristiche alla base delle Single Page Applications e' la possibilita' di richiedere dati ad un server senza dover ricaricare la pagina, possibilita' introdotta dall'oggetto globale XMLHttpRequest.
> var req = new XMLHttpRequest()
> req.open("GET", "http://localhost:9999/bundle.css", false);
> req.send(null)
> req.status
200
> req.statusText
"...
..."
> req.responseText
"...
..."
Negli ultimi anni si e' molto diffuso un formato attraverso il quale scambiare dati tra client e server: JSON.
JSON e' una notazione derivata dalla sintassi con la quale si rappresentano i tipi dati JavaScript descritti in precedenza (numeri, stringhe, booleani, oggetti e array).
JSON prevede pero' una sintassi leggermente piu' restrittiva:
{
"users": ["admin", "guest", "user01" ],
"loggedUsers": ["admin"]
}
JavaScript fornisce un oggetto JSON contenente alcuni metodi di utilita' con i quali convertire le nostre strutture dati in una stringa di testo in formato JSON (JSON.stringify) e convertire una stringa in formato JSON nelle strutture dati JavaScript corrispondenti (JSON.parse):
> var data = { users: ["admin", "guest"] }
> JSON.stringify(data);
'{"users":["admin","guest"]}'
> JSON.parse('{"users":["admin","guest"]}')
Object { users: Array[2] }
Nella programmazione le eccezioni sono errori che si verificano durante l'esecuzione del programma e in genere sono dovute all'input dell'utente (che non e' prevedibile).
Poiche' JavaScript e' un linguaggio interpretato e dinamico, le eccezioni potrebbero anche essere verificarsi a runtime a causa di una parte dell'applicazione che non era ancora stata eseguita.
La gestione di queste condizioni di errore e' affidata ad una apposita struttura di controllo:
try {
JSON.parse("{...questo non e' JSON");
} catch(e) {
console.error("EXCEPTION PARSING JSON", e);
}
switch e' una struttura di controllo simile ad una serie di *if / else in cascata:
if (role == "admin") {
...
} else if (role == "guest") {
...
} else {
...
}
switch (role) {
case "admin":
...
break;
case "guest":
...
break;
default:
...
}
Creare uno script con due funzioni:
function sum(a, b) {
return a + b;
}
function readAndSumNumbers() {
var first = prompt("Insert the first number");
var second = prompt("Insert the second number");
var total = sum(parseFloat(first),
parseFloat(second));
document.write("Total: " + total);
}
<!DOCTYPE html>
<html>
<head>
<script>
function sum(a,b) {
...
}
function readAndSumNumbers() {
...
}
readAndSumNumbers()
</script>
</head>
<body>
</body>
</html>
Cosa succede se una delle due stringhe in input non fosse una stringa numerica?
Modificare l'esercizio precedente:
if (isNaN(first) || isNaN(second)) {
...
} else {
...
}
function readAndSumNumbers() {
var first = prompt("Insert the first number");
var second = prompt("Insert the second number");
if (isNaN(first) || isNaN(second)) {
document.write("ERROR: at least one of the input is not a number");
} else {
var total = sum(parseFloat(first), parseFloat(second));
document.write("Total: " + total);
}
}
Accumulare un array di oggetti todo, definendo le seguenti funzioni:
{ text: "stringa"}
...
...
function readTodoList() {
var todoList = [], newInput;
while (true) {
newInput = prompt("Insert a new todo or an empty string to exit");
if (newInput.length == 0) {
break;
}
todoList.push({ text: newInput })
}
return todoList;
}
function printTodoList(todoList) {
document.write("<ul>");
for (var i = 0; i < todoList.length; i++) {
document.write("<li>" + todoList[i].text + "</li>");
}
document.write("</ul>");
}
Generalizzare l'esempio precedente per sommare un numero variabile di input, definendo le seguenti funzioni
...
...
function sumArray(numbers) {
var total = 0;
for (var i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
function readAndSumNumbers() {
var numbers = [], numbersLen,
numbersLenStr;
while (isNaN(numbersLenStr)) {
numbersLenStr = prompt("How many numbers?");
}
numbersLen = parseInt(numbersLenStr);
...
function readAndSumNumbers() {
...
while(numbers.length < numbersLen) {
var newInput = prompt("Insert a number");
if (isNaN(newInput)) {
alert("'" + newInput + "' is not a number.");
continue;
}
numbers.push(parseInt(newInput));
}
document.write("Total: " + sumArray(numbers));
}
Partendo dall'esercizio 3:
function printTodoList(todoList) {
var el = document.querySelector("ul#todo-list");
for (var i = 0; i < todoList.length; i++) {
el.innerHTML += "<li>" +
todoList[i].text +
"</li>";
}
}
La funzione printTodoList dovra' attendere l'effettiva presenza del tag ul all'interno del documento.
window.onready = function() {
var todoList = readTodoList();
printTodoList(todoList);
}
oppure
window.addEventListener("ready", function() {
...
}, false);
<body onload="getJSON()">
todos.json
che simuli la struttura di una TODO list{
"total": 2,
"todos": [{
"completed": true,
var req = new XMLHttpRequest();
req.open('GET', 'todos.json', false);
req.send(null);
if(req.status == 200) {
console.log(req.responseText);
} else {
...
}
var j;
if(req.status == 200) {
j = JSON.parse(req.responseText);
j = JSON.parse(req.responseText);
j.todos.forEach(function(todo) {
...
});
function getJSON() {
var req = new XMLHttpRequest();
req.open('GET', 'todos.json', false);
req.send(null);
var j;
if(req.status == 200) {
j = JSON.parse(req.responseText);
j.todos.forEach(function(todo) {
var todoItem = "<li>" + todo.text + "</li>";
document.querySelector("ul").innerHTML += todoItem;
})
} else {
console.error("ERRORE nel prelevare i dati");
}
}