Programmation Javascript (NFA041)

Bases de programmation illustrées en javascript

Olivier Pons
(olivier.pons@lecnam.net)

2022

La boucle d'interaction

Tout les premiers exemples sont à tester à ce niveau.

Dans les exemples ce que vous tapez est précédé du caractère d'invite \(>\).
Le réponse de l'interpréteur suit.

> 2+4         // <--- ce qu'on tape
6             // <--- la réponse 
> 

Les Commentaires

Tous le code javascript que nous écrirons comprendra des commentaires.

Dans le but d'expliquer ou de documenter le code.

Des bouts de code ignorés lors de l'évaluation du code par l’interpréteur javascript

du code javascript // partie commentée
... du code javascript 
/*
 partie de code commentée
 */
... suite du code

Des valeurs de base

Elle peuvent être de différent types.

Des nombres (number)

> 7
7
> 3.14
3.14
> 10E6
10000000
> 2e+20
200000000000000000000
> Infinity
Infinity

...

Pas de différence (de type) entre flottant et entier. C'est Numberpour les deux.

Des chaînes de caractères (string)

Délimitées par des guillemets simples '...' ou doubles "....".

> "bonjour"
'bonjour'
> 'ca va ?'
'ca va ?'
>""
'' 
...

Il n'y a pas de type spécifique aux caractères.
Un caractère est juste une chaîne composée de cet unique caractère.

On peut aussi délimiter les chaînes par guillemets inversés (back quote) ` . On verra plus loins que cela permet d'interpreter du code dans une chaîne.

Des booléen (boolean)

2 valeurs: true et false

> true        //vrai
true
> false       //false
false

Deux valeurs spéciales

> undefined ()
undefined
>null
null

On reparlera de la première au moment des variables et de la seconde au moment des "objets"

Quelques opérations sur les valeurs de base

Ou l'on voit la boucle d'interaction javascript (Read, Eval, Print, Loop) comme une calculatrice.

Les opérations disponibles dépendent des types:

Sur les nombres:

> 2+3
5 
>  2.45*45
1 10.25000000000001
>  99.9/3
3 3.300000000000004
> 21/2          
10.5
>  4/8                     
0 .5
>  100000000000000
1 00000000000000
>  1000000000000000000000   //notation scientifique
1 e+21
>  2e+20
2 00000000000000000000

> 9%2    //modulo
1

> a= -1/0;     //on a des elements représentants l'Infini
-Infinity

> 
> Math.sqrt(36)
6
> Math.round(99.9)
100
> Math.floor(99.9)
99
> Math.sin(Math.PI)
1.2246467991473532e-16

> Math.random() // nombre aléatoire en 0 et 1
0.6259868469814811
> Math.random()
0.8644534713097394

> Math.sqrt(-1)    // la racine d'un nombre negative n'existe  
NaN                // on renvoi un : Not a Number

Remarque

Pour la division euclidienne (entière) de x par y, on pourra faire :

      q= Math.floor(x/y);

(Math.floor() calcule la partie entière).

Sur les chaines

  > "bonjour".length
  7
  >
> "bonjour "+"ça va ?"
'bonjour ça va ?'
> "bonjour"[0]
'b'
> "bonjour"[5]                   
'u'
> "bonjour".charAt(5)          // equivalent du precedent
'u'
> "bonjour"["bonjour".length-1] //dernier élément
'r'
> "bonjour"["bonjour".length]   //on va trop loin...pas de valeur 
undefined
> 
> "bonjour".indexOf("j")
3
> "bonjour".indexOf("ou") 
4
> "bonjour".indexOf("oui") //si pas present renvoi -1
-1
> 
>"bonjour".substring(3,6)
'jou'
> "A".charCodeAt(0)
65
> String.fromCharCode(66) 
'B'  
> String.fromCharCode(67)      // les caractères sont dans l'ordre alphabétique
'C'
> "a".charCodeAt(0)            // mais les minuscules sont après les majuscules
97
> "b".charCodeAt(0)
98

Sur les booléens

> true && false
false
> 
> false || true
true
> ! true
false
> 

Remarque

&& et ||sont paresseux c'est à dire:

si le premier argument de && est faux il ne calcule pas le second
si le premier argument de || est vrai il ne calcule pas le second

sur toutes les valeurs

les comparaisons ===, > < >= <= !==

On rencontrera aussi, == et != mais ils sont est plein de pièges et donc à éviter en général (nous verrons pourquoi)

3<=3
true
> 1E10>Math.pow(2,100)
false
> "3"===3
false
> "3"==3   // A EVITER conversion implicite source d'erreur !
true
> true<false
false
> false<true
true
! true===false
> ! true===false
true
> 

Attention

Les comparaisons sur les chaînes utilisent l'ordre alphabétique dans l'encodage des caractères (UTF-16).
Les majuscules sont situées avants les minuscules.

> "ABC"<"DEF"
true
> "abc"<"def"
true
> "ABC"<"def"
true
> "abc"<"DEF"
false
> 

> "111">"12" //sur des chaines le caractère '1' est avant le caractère '2' dans "*l'alphabet*"
false
> 111>12      //sur des nombres
true

Vérification de type: typeof

> typeof "une chaine"
'string'
> typeof "bonjour"[1]
'string'
> > typeof ("1"+2)
'string'
> 

> typeof NaN  //Not a Number est de type number !
'number'


> typeof Undefined
'undefined'
> typeof null
'object'              // on parlera du type object plus loin ;-)
> 

Remarque

NaN est un number qui correspond à une operation impossible sur les entiers.
il se propage c'est à dire que si une partie d'un calcul est impossible alors tout le calcul est impossible

>  33*(44-44)/0
NaN
>   

NaN est le seul nombre... pas égal à lui même. Pour le tester, il faut utiliser isNaN() :

a= 0/0;
console.log(a==NaN); // affiche false
console.log(isNaN(a)); // affiche true

Et si on mélange ...

C'est source d'erreur pénible à detecter...


> "deux"+2
'deux2'
> "2"+2
'22'
> "deux"-2
NaN
> 

Attention:

= ne sert pas a comparer, c'est un opérateur d’affectation

Retour récapitulatif sur les opérateurs de comparaisons

Opérateur : Description
a == b : Égal, après conversion de type
a != b : Différent, après conversion de type
a === b : Égal et de même type
a !== b : Différent ou de type différent
a < b : Strictement plus petit après conversion de type
a > b : Strictement plus grand, après conversion de type
a <= b : Plus petit, après conversion de type
a >= b : Plus grand, après conversion de type

Constante, variables

On a vu la notion de valeur (et les calculs qu'on peut faire avec ces valeurs).

On peut stoker ces valeurs dans des variables (ou des constantes).

On peut conceptualiser une variable comme une boite, dans laquelle on peut stoker l'information.

On peut éventuellement modifier le contenue de la boite.

Concrètement c'est un nom qui renvoie à une position en mémoire dont le contenu peut potentiellement prendre successivement différentes valeurs pendant l'exécution d'un programme.

Constante, variables et affectation en javascript

Exemples :

> let pi = 3.1415926;
undefined
> let _2pi = 2*pi;  // le nom ne peut commencer par un  chiffre !
undefined
> let 2pi= 2*pi;    // le nom ne peut commencer par un  chiffre !
let 2pi= 2*pi;      // donc erreur de syntaxe !
    ^
Uncaught SyntaxError: Invalid or unexpected token

> pi = 3.14;       //mise à jour de la variable
3.14

> const vitesseDeLaLumiere= 299792458
undefined
> vitesseDeLaLumiere= 300000                        // on ne peut modifier une constante
Uncaught TypeError: Assignment to constant variable.

> let ecrivain="Romain Gary"
undefined
> let ecrivain="Emile Ajar"         // on ne peut la redéfinir
Uncaught SyntaxError: Identifier 'ecrivain' has already been declared
> ecrivain="Emile Ajar"            // mais on peut la mettre a jours
'Emile Ajar'
> 

Remarques

  1. Si une variable n'existe pas (ie. elle n'a pas été déclarée), lui donner une valeur la déclare automatiquement.
    Mais, (surtout dans les fonctions) ça ne fait pas ce que l'on pense (cela crée une variable globale dont on reparlera au moment des fonctions)

  2. On rencontrera souvent le mot clef (historique) var pour déclarer une variable:
    Mais il vaut mieux ne plus l'utiliser (même si je le ferais parfois par habitude ou à propos) car sont utilisation peut être source de bug. On en reparlera à la fin des fonctions.

> let a= 'salut';
undefined
> typeof a; 
'string'
> a= 3;
3
> typeof a; 
'number'
> a= a + 2.5;
5.5
> typeof a;
'number'
> a= a + "3" 
'5.53'
> typeof a;
'string'
> 
> let b=true
undefined
> let d="c'est vrai"
undefined
> let e=b+d
undefined
> e
"truec'est vrai"
> 

Remarques

> 0+(1+"0")     // les conversions implicite font que 
'010'           // l'opérateur + n'est plus associatif !
> (0+1)+"0"
'10'
> 

Conversion implicites et explicite

Plutôt que de compter avec les conversions implicite on préférera des conversions explicites:

// > Number("33")   // chaîne vers nombre
33
> String(33)    // nombre vers chaîne
'33'
> Number("deux")  // impossible de convertir
NaN               // on renvoi un Not a Number
> Boolean("true")  //chaîne vers Booléen 
true

> typeof Number("33")  
'number'
> Number("33cm")
NaN
> parseInt("33cm")    // plus souple que Number ignore ce qui suit les nombres
33
> parseInt("trente trois") // mais NaN si pas de nombre !
NaN
> 

Opérateur sur les variables (affectation et incrémentation)

L’opérateur d'affectation = qui associe une valeur (résultat de l'evaluation d'une expression) n'a rien à voir ni avec le = utilisé en maths ni avec les opérateurs de comparaisons === et ==. Il n'est pas symétrique.

> let i=3;         // on déclare et initialise i avec la valeur 3
undefined
> i=4              // on met i à jours; la nouvelle valeur est renvoyée
4
> i                // on demande la valeur contenue dans i
4
> i+=5            // on ajoute 5 a la valeur contenue dans i et on stock  
9                 // le résultat dans i; la nouvelle valeur est renvoyée
> i               // on demande la valeur contenue dans i
9
> i-=2            // on enlève 2 a la valeur contenue dans i et on stock  
7                  // le résultat dans i; la nouvelle valeur est renvoyée

> let s="une chaîne"  // on déclare et initialise i avec la valeur "une chaîne" 
undefined
> s+=" la suite"      //on concatène la valeur contenue dans  s et la chaîne "la suite" 
'une chaîne la suite' //la nouvelle valeur est renvoyée
> s                   // on demande la valeur contenue dans s
'une chaîne la suite'
> s*=" mais pas avec * sur les chaines" // l'opérateur `*`nest pas défini sur les chaîne
NaN                                     // javascript essai de les convertir en nombre
                                        // ce n'est pas possible il renvoi un NaN
                                        // la * par un  NaN produit une  NaN
                                        //(comme toute operation sur un NaN)
> let s2="2"        // on déclare et initialise s2 avec la valeur "2" 
undefined           // elle est de type string
> let i2=s2*s2      // on déclare et initialise 2 avec la valeur 
undefined           // le résultat de l'evaluation de s2*s2.
                    // comme `*`n'existe pas sur le chaines.
                    // javascript convertie la valeur de s2 (la chaîne "2")
                    // vers le nombre 2, puis évalue la multiplication
> i2                // i2 vaut maintenant 4
4
>                    
> let i=3;
undefined
>   i++;
3
>   i;
4 
> ++i;
5

i++ augmente i de 1 et renvoi l'ancienne valeur de i;
++i augmente i de 1 et renvoi la nouvelle valeur de i;

idem pour --

Remarque

  1. Appliquer ces opérateurs sur autre choses que des nombres (number) entraîne des conversions implicites.
    Il vaut mieux éviter.
  2. Quand le contenu des variables est une chaîne il peut évidemment contenir tous caractère UTF-16, en particulier les caractères accentués. Bien que ce soit aussi possible aussi dans les noms de variable il faut mieux l'éviter.

Les constructions de bases

Le code JavaScript est une suite d'instructions, (certaines peuvent être des des affectations, ou des déclarations de fonctions ...)

Les instructions sont séparées par des passages à la ligne ou par le caractère ;

On suggère d'utiliser systématiquement ;, mais le passage à la ligne reste toujours un séparateur

On peut écrire :

  let francs=10;let euros= 6.56* francs; console.log(euros);

ou

  let francs=10
  let euros= 6.56* francs
  console.log(euros)

ou

  let francs=10;
  let euros= 6.56* francs;
  console.log(euros);

La dernière forme est la plus lisible et plus robuste à des réorganisation de code.

Et comme les interprètes javascript donne généralement la ligne de la premiere erreurs, il vaut mieux minimiser le nombre d'instruction par ligne.

Remarque

Si vous tester dans le navigateur vous pouvez remplacer console.log par alert.

Les accolades { } délimitent des blocs et ne sont pas suivies de ;

Instruction vs Expression

On distingue les EXPRESSIONS et les INSTRUCTIONS, qui toutes deux vont contenir des variables. L'evaluation d'une expression calcule une valeur, tandis que l'évaluation d'une instruction exécute le code correspondant qui a un effet sur l’environnement (crée des variables, modifie la valeurs des variables, affiche à l'écran, écrit sur une socket reseau ...) !

let a = 3 ; // instruction  dont l'execution, declare 
            //a et l'initialise avec la valeur 3 
2*a*a;      // expression évalué à la valeur 18
a=2*a;     // instruction qui évalue l'expression 2*a 
           //et met a jours a avec la valeur en résultant

console.log("a vaut ",a);  // instruction qui affiche a vaut 6

Les instructions renvoient soit la dernière expression évaluée durant leur execution soit undefined .

« vrai » programme, hors de la boucle d'interaction

Le travail interactif au toplevel est pratique pour de petits calculs et pour se familiariser avec javascript. Mais un « vrai » programme, ce n’est pas ça!!

Un programme = une suite d’instructions qui sont exécutées sans interruption et sans afficher de résultats. Il faut forcer l’affichage en utilisant l’instruction operation de sortie.

Entrée sortie en javascript

On appelle sortie la possibilité d'écrire généralement à l'écran ( mais pas que ).

On appelle entrée la possibilité de lire généralement au au clavier ( mais pas que ).

Sorties dans le navigateur:

Sorties dans node

Entrées dans le Navigateur:

Entrées dans node:

Compliquées...

> const prompt = require('prompt-sync')();
undefined
> let nom=prompt('entrez un nom :');
entrez un nom :olivier
undefined
> nom
'olivier'
> 

Dans la plupart des exemples du début du cours (excepté quand on étudiera la manipulation du DOM, ou l’asynchronisme), nous utiliserons prompt pour lire au clavier et console.log pour écrire à l’écran.

L'instruction console.log

> let a = 3
undefined
> console.log('Le carré de a vaut',a*a,'et non 5')
Le carré de a vaut 9 et non 5
undefined
> 

interlude (demo): programme de salutation

(hors de la boucle d'interaction)

  1. dans le navigateur

  2. dans node

Fonctions

Comme on a nommé des valeurs avec la notion variable .

La notion de fonction va nous permettre de nommer un enchaînement d'instructions pour pouvoir le réutiliser plus tard.

Pour augmenter l’intérêt de la chose ce bloc d'instruction peut être paramétré

On peut définir des fonctions :

function f(x1, ..., xn) {
    // instructions
};
function volumeSphere(R){
  return (4/3)*Math.PI*R*R*R;
}

> volumeSphere(9)
3053.628059289279
> 
> volumeSphere()
NaN

> volumeSphere(1,2,3,4)
4.1887902047863905

> typeof volumeSphere
'function'
> 
 function saluer(nom){
  const salutation="Bonjour "+nom;
  console.log(salutation)
  }

> saluer("olivier")
Bonjour olivier
undefined
> saluer()
Bonjour undefined
undefined
> saluer("olivier","pons")
Bonjour olivier
undefined
 
> 

Remarque

Depuis ES6 il existe une autre syntaxe pour les fonction, la notation fléchée plus proche de la notation mathématique

> let f=(x)=>x+2;
undefined
> typeof f
'function'
> f(3);
5
> 

On verra au moment des objets que leur sémantique n'est pas tout a fait la même mais d'ici la elles sont interchangeable.

Remarque

Les fonction sont des valeurs comme les autres en javascript.
On peut les manipuler, les passer en argument ou les renvoyer comme résultat mais nous y reviendront...

Les structures de contrôle

instructions conditionnelles

L'instruction conditionnelle if permet de prendre une décision.

La forme générale est la suivante:

if (condition) {
  // code a executer si c est vrai
} else {
      // code a executer sinon
}

conversion implicite vers les valeurs booléennes

Si a condition ne s'évalue pas a une valeur booléen, il peut y avoir des conversions implicites

> if(0){a="zero"} else{a="un"}  // 0 est converti en false
'un'
> if(1){a="zero"} else{a="un"}  // les autre valeurs en true
'zero'
let c=2
> if(c){a="zero"} else{a="un"}
'zero'
// il vaut mieux écrire
>if(2===c){a="zero"} else{a="un"}

on peut regarder ici

Expressions conditionnelles

Le if précédent est une instruction. Il choisi le bloc d'instruction à executer mais ne renvoie pas de valeur. (meme si la boucle REPL affiche la valeur de la dernière expression évaluée) Il ne peut donc servir de membre droit à une affectation :

> let v;
undefined
> v=if(c){a="zero"} else{a="un"}
v=if(c){a="zero"} else{a="un"}
  ^^

Uncaught SyntaxError: Unexpected token 'if'
> v=if(c){"zero"} else{"un"}
v=if(c){"zero"} else{"un"}
  ^^

Uncaught SyntaxError: Unexpected token 'if'
> 

Pour cela on peut définir des Expressions Conditionnelles
Dont la forme générale est la suivante:

(condition)?valeurSiVrai:ValeurSiFaux

Ici encore La condition doit s'évaluer en une valeur booléen.
Si elle est vrai la valeur de l'expression est celle qui suit le ? et precede de :
Si elle est fausse la valeur de l'expression est celle qui suit :

> let pval=(15>5)?99:1;             //  15>3 s'évalue a true donc c'est 99 qui est évalué      
undefined                           // et c'est 99 qui est affecté a pval
> pval;
99
> ((pval >2 && pval <100)?100:200)+33;  //(pval >2 && pval <100) s'évalue a true
133                                     // donc l'expression conditionnelle s'évalue a 100
                                        // on ajoute 33 pour obtenir 133

> let qval=(9<=1)?"valeur1":"valeur2";   // idem avec des chaine
undefined
> qval;
'valeur2'
> (99%4===0)?"quatre divise 99":"quatre ne divise pas 99"
'quatre ne divise pas 99'

> let nbPersonne=3;
undefined

> "il y a "+nbPersonne+" personne"+((nbPersonne>1)?"s":"")   //une utilisation classique
'il y a 3 personnes'
> 

> (Math.random()>=0.5)?1000:99      //utilisation de random dans la condition, cela choisit un nombre entre O et 1
1000                                // donc 1 chance sur 2 d'être inférieure à 0.5 
> (Math.random()>=0.5)?"chaine":99  //note: les 2 valeurs possibles peuvent être de type different
99                                  //ici string et number 
                                    //mais c'est souvent source d'erreur et il faut mieux éviter en général 

l'instruction d'aiguillage,switch

Elle peut dans certains cas remplacer une série (souvent peu élégante) de if … else. On l'utilise lorsque les cas à gérer sont nombreux.


> var reponseHttp=300;
> //....
> switch(reponseHttp){
    case 200: console.log("Tout va bien !");break;
    case 300: console.log("Vous allez ailleurs !");break;
    case 400: console.log("Hum... Vous êtes perdu ?");break;
    case 500: console.log("Harg ! Quelque chose a mal tourné !");break;
    default : console.log("Heuu... Ce n'est pas un code correct");break;
  }

Vous allez ailleurs !

Attention:

Si on ne met pas une instruction break, le script exécutera les instructions pour le cas correspondant et aussi celles pour les cas suivants jusqu'à la fin de l'instruction switch ou jusqu'à une instruction break.

>switch(reponseHttp){
    case 200: console.log("Tout va bien !");
    case 300: console.log("Vous allez ailleurs !");
    case 400: console.log("Hum... Vous êtes perdu ?");
    case 500: console.log("Harg ! Quelque chose a mal tourné !");
    default : console.log("Heuu... Ce n'est pas un code correct");
  }

Vous allez ailleurs !
Hum... Vous êtes perdu ?
Harg ! Quelque chose a mal tourné !
Heuu... Ce n'est pas un code correct
undefined

Répétition et boucles itératives

Les boucles sont utilisées pour répéter une suite d’instructions. La façon la plus générale d’écrire une boucle en Javascript est d’utiliser for ou while dont la syntaxe est hérité du langage C.

## boucle for à la C

La forme générale est la suivante:

 for(initialisations ; condition ; incrémentations)  {
        //corps de la boucle for
        //ie. instructions a répéter
}
  1. les initialisations sont exécutées une seule fois avant de commencer.
  2. la condition est évaluée et vérifiée avant chaque repetition
  1. puis les incrémentations sont exécutées et on repasse en 2

Exemple: Compter jusqu'a 5 en affichant les nombres


> for (let i = 0; i < 6; i = i + 1) {
          console.log(i);
}
      //on a initialise i a 0 , i est inférieur a 6 donc rentre dans le corps 
0     // et on affiche i (donc 0).  on increment i qui vaut maintenant 1        
1     //i est inférieur a 6 donc rentre dans le corps et on affiche i  (1) et on increment i qui passe a 2
2     //i est inférieur a 6 donc rentre dans le corps et on affiche i  (2) et on increment i qui passe a 3
3    // ...
4
5    //i est inférieur a 6 donc rentre dans le corps et on affiche i  (6) et on increment i qui passe a 6
     // n'est pas inférieur a 6 donc on sort de la boucle.

Exemple: Faire la somme des 10 premiers entiers

> let somme=0
undefined
>  for (let i = 0; i <= 10; i = i + 1) {
...     somme+=i;
... }
55
> 

boucles while

Il y a deux types de boucle while dont les formes générales sont les suivantes. Dans la seconde forme on passe au moins une fois dans le corps de la boucle.

while ( condition )  {
        //corps de la boucle while
        //pensez a faire varier la condition...
}

do {
   //corps de la boucle do ...while
   // on y passe au moins une fois
   //pensez a faire varier la condition...
} while (condition);
let i=0;
while (i<10){
  console.log(i);
  i=i+1; // Attention si on l'oubli c'est la boucle infinie...
}
let age;
let reponse=false;;

do{
  age=prompt("entrez votre nom ?");
  reponse=prompt("age :"+age+" est correct ?")
} while(reponse != "oui")                         // tant que l’utilisateur n'a pas rentre oui
                                                  //on boucle

La boucle for est a priori plus conçu pour les répétions dont donc on connaît le nombre mais le choix de l'utilisation de l'une ou l'autre des boucles reste avant tout une affaire de style.

Attention aux boucles infinies

On veut que nos programme terminent donc il faut a priori éviter les boucles infinies !
(sauf si on écrit un serveur ou un interpréteur comme la boucle REPL...)

for (let i=1;i>0;i=i+1){            // Dans node sortir par `Ctrl + C``
  console.log(i);                   // dans le Navigateur risque de plantage
}

Récursivité et récurrence

Une autre façon d'obtenir des répétitions est l'écriture de fonctions récursive.

On parle de fonction recursive lorsqu'une fonction s'appelle elle même
(directement ou en appelant une fonction qui l'appelle).

Pour évitez la boucle infinie, il faut prévoir un cas d'arrêt et 'évolué' vers ce cas a chaque nouvel appel. La forme générale ressemble à ceci:

function maFonctionRecursive(paramètres){
  if(cas d'arrêt){return uneValeur}                  // cas de base
  else{
    ....
    return maFonctionRecursive(nouveauxParamètres)  // appel récursif
  }
}

Exemple

Considérons

sommeCarres(n)=\(1+2^2+3^2\dots+n^2\)

En regroupant les (n-1) premiers carrés de droite on obtient la relation de recurrence:

sommeCarres(n)=sommeCarres(n-1)+\(n^2\)

avec la convention que pour n\(=0\) on a sommeCarres(n)\(=O\)

Ce qui en javascript s'écrit:

function sommeCarres(n){
  if(n===0) {return 0}                 // cas d'arrêt
  else {return (n*n)+sommeCarres(n-1)} // appel récursif
}

Exemple

Exemple plus compliqué les tours de hanoi

function mouvement( de, vers) {
  console.log("Déplace un disque de la tige "+de+" à la tige "+vers);
}
 
 
 function hanoi (depart, milieu, arrivee,n){
    if(n>0){ 
      hanoi(depart,arrivee, milieu, (n - 1));
      mouvement(depart, arrivee);
      hanoi(milieu, depart, arrivee, (n - 1));
      
    }
 }

Et avec 3 disques...

> hanoi("A","B","C",3)
Déplace un disque de la tige A à la tige C
Déplace un disque de la tige A à la tige B
Déplace un disque de la tige C à la tige B
Déplace un disque de la tige A à la tige C
Déplace un disque de la tige B à la tige A
Déplace un disque de la tige B à la tige C
Déplace un disque de la tige A à la tige C
undefined
FIN FONCTIONS SIMPLES

Les tableaux

les tableaux (Array)

Le moyen le plus simple de représenter une collection de valeur.

ils représentent une séquence finie d'éléments auxquels on peut accéder efficacement par leur position, ou indice, dans la séquence.

let  tab= []; // Création du tableau (vide)
undefined
> tab[0]="zero"  //ajout dans la premiere case
'zero'           //d'indice 0
> tab[1]="un"   //ajout dans la case d'indice 1
'un'
> tab[2]="deux"
'deux'
> tab
[ 'zero', 'un', 'deux' ]
>  
> tab[5]="cinq"
'cinq'
> tab
[ 'zero', 'un', 'deux', <2 empty items>, 'cinq' ]
> 
> let tab2=["il","fait","beau"]
undefined
> tab2
[ 'il', 'fait', 'beau' ]
> 
> tab[1]+" et "+tab[2]
'un et deux'
> tab[2].toUpperCase()
'DEUX'
> 
> tab[4]
undefined
> tab[10]
undefined
> tab[10]===undefined
true
> tab.length
6
> tab2.length
3
tab2[tab2.> tab2[tab2.length-1]
'beau'
> 

Attention

> tab2[tab2.length]  //toujours ..
undefined            // en length on est trop loin
                     // car on commence à 0

Parcourir les tableaux

Avec des boucles (for, while,...)

> let s= 0;
undefined
> for (i= 0; i < tab3.length; i++) {
    s= s + tab3[i];
   }
'0azety'
> s;
'0azety'
> 

Avec des fonction recursive

Exemple : la somme de éléments

let tabNum=[2,4,6,8,10];
function sommeAux(t,n){
  if (n<0) {
    return 0
  }else{
    return t[n]+sommeAux(t,n-1);
  }
}
function somme(t){return sommeAux(t,t.length-1)}
> somme(tabNum)
30

On verra des méthodes spécifiques aux tableaux plus tard.

FIN DU PREMIER SURVOL

tableau suite

s= tab.join(""); // s vaut undeuxtrois
s= tab.join(":"); // s vaut un:deux:trois
tab= ["un", "deux", "trois"]; 
tab.push("quatre"); // renvoie le nouveau `length`
// tab[3] vaut maintenant "quatre"

inversement, pop() supprime le dernier élément du tableau, et le retourne:

  tab= ["un", "deux", "trois"];
  a= tab.pop();
  // tab vaut ["un", "deux"] et a vaut "trois"

Voir aussi shift et unshift, slice

Supprimer/remplacer des éléments dans un tableau

Pour supprimer des éléments dans un tableau, on peut utiliser la méthode splice. t.splice(indice,nombre)

Supprimer nombre éléments dans t à partir de l'indice indice.

En option :

t.splice(indice,nombre, e1, e2,...); supprime nombre éléments dans t à partir de l'indice indice et insert e1, e2...

Fonctions d'ordre supérieur

Les fonctions d’ordre supérieur sont des fonctions dont les arguments ou les résultats sont eux-mêmes des fonctions. Une fonction d’ordre supérieur est parfois appelée une fonctionnelle

On dit que les fonction sont des objet de premiere classe du langage

Fonction dont le résultat est une fonction

Recursion profonde et recursion terminale

Considérons l'archetype des fonctions récursives, la fonction factorielle.

Une récurrence évidente permet d'écrire~:

function fact(n){
   if (0===n){
        return 1;          /* cas de base */
    }else {
      return n*fact (n-1); /* cas de récurrence */
    }
}

Détaillons l'évaluation de fact (3)

fact (
 -> if n=0  then 1 else  n*fact (n-1)                        avec la liaison n=3
 ->  n*fact (n-1)                                            avec la liaison n=3
    -> 3*fact (2)
    ->3 * ( if n=0  then 1 else  n*fact (n-1))               avec la liaison n=2
    ->3 * (  n*fact (n-1))                                   avec la liaison n=2
       ->3 * ( 2 * (fact (1)))
       ->3 * ( 2 * ( if n=0  then 1 else  n*fact (n-1)))     avec la liaison n=1
       ->3 * ( 2 * (  n*fact (n-1) ))                        avec la liaison n=1
           -> 3 * ( 2 * (1 *(fact 0)))
           -> 3 * ( 2 * ( 1 * ( if n=0  then 1 else  n*fact (n-1))))   avec la liaison n=0
           -> 3 * ( 2 * ( 1 * 1))
       <- 3 * ( 2 * ( 1))
    <- 3 *  2
<-6

On constate que la multiplication 3*fact (2) ne peut être faite avant de connaitre le résultat de (fact 2) ; puis que pour évaluer ( 2 * fact (1)) on doit attendre de connaître le résultat de (fact 1) ; et enfin que la multiplication (1 *fact (0)) ne peut être faite qu'après le calcul de (fact 0); on peut alors remonter en cascade (c'est les flêches <-) les multiplications restées en attente. Pour pouvoir effectuer cette remontée, il faut donc avoir stocké les opérations en attente. Ce stockage nécessite l'intervention d'une pile. On remarque par ailleurs que le nombre de résultats à stocker est proportionnel à l'argument n donc que plus cet argument est grand plus on doit stocker de résultats sur la pile. Dans certains cas, ce stockage peut saturer la mémoire [^]

factIt 3
->(aux 3 1) 
 -> match p with  0 -> acc |_-> aux (p-1) (acc * p)          avec  p=3 et acc =1  
   ->aux 2 3
   ->match p with  0 -> acc |_-> aux (p-1) (acc * p)         avec  p=2 et acc =3
      ->aux 1 6
      ->match p with  0 -> acc |_-> aux (p-1) (acc * p)      avec  p=1 et acc =6
        -> aux 0 6
        ->match p with  0 -> acc |_-> aux (p-1) (acc * p)    avec  p=0 et acc =6
            ->6

Ainsi le calcul de aux(3,1) se ramène à celui de aux(2,3) qui lui même se ramène à aux(0,6) qui renvoie 6. Les résultats intermédiaires sont stockés dans l'accumulateur acc et à chaque étape le nouveau résultat intermdiaire vient remplacer l'ancien. A aucun moment on a des calculs en suspens à mémoriser, on n'a donc pas besoin de pile. La place mémoire nécessaire est donc constante et indépendante de l'argument n.

Dans ce second type de réursivité, l'appel récursif n'est pas enveloppé (c'est-à-dire qu'il n'est pas argument d'une opération) , il est "en position terminale".

En résumé

    **    Existence d'une enveloppe $\Leftrightarrow$ "récurrence profonde"  $\Leftrightarrow$  pile $\Leftrightarrow$ Mémoire nécessaire dépendant de l'argument**
** Pas d'enveloppe $\Leftrightarrow$ "récurrence terminale" $\Leftrightarrow$ pas besoin de pile $\Leftrightarrow$ M<E9>moire nécessaire indépendante de l'argument}

Toute fonction récursive terminale peut s'écrire de façon itérative.

function factFor(n){
   let result =  1; 
   for (let i=1; i<n;i++){
      result =i* !result;
    } 
    return result;
}

Remarque

Bien que la norme prevois l'optimisation de la récursivité terminale. Les implantations courantes (a l’exception de Safari)

recursion mutuelle

Les tableaux et la mémoire

les object comme tableaux associatifs (dictionnaire)

On la notion de tableau associatif permet d'associer une clef à une valeur.

En javascript le type n'existe pas c'est un cas particulier d'utilisation des objets.

var dico= {};
dico["java"]= "langage de programmation orienté objet";
dico["lisp"]= "langage de programmation fonctionnel";

Autre méthode de création :

  var t= {"java": "langage de programmation orienté objet", 
          "lisp": "langage de programmation fonctionnel"};

Pour supprimer un élément dans un tableau associatif : javascript delete t["java"];

parcourir un tableau

## boucles classiques et recursion ## fonctions spécifiques aux tableaux

plus de choses sur les chaînes (1)

"bonjour"[1]; // o
var c="monde";  c.charAt(0) // m
"ABC".charCodeAt(0) // 65
String.fromCharCode(65) // 'A'

//si > 65535 plusieurs codet
var code="😀".charCodeAt(0) // 65
String.fromCharCode(code) // pas  "😀"

let hex = "😀".codePointAt(0).toString(16)
let emo = String.fromCodePoint("0x"+hex); 
console.log(hex, emo);  // affiche 1f600 😀

plus de choses sur les chaînes (1)

let s= "un,deux,trois";
let t= s.split(",");  // t est un tableau de 3 éléments 
s= "un,deux,trois";
i= s.indexOf("tr"); // i vaut 8

Chaînes et nombres

Deux fonctions de conversion sont particulièrement intéressantes: + parseInt(CHAINE) : analyse chaîne comme un entier. Renvoie la valeur NaN (not a number) en cas d'échec. + parseFloat(CHAINE) : analyse chaîne comme un entier. Renvoie la valeur NaN en cas d'échec.

Notez que ces deux fonctions sont très permissives. Elles acceptent des chaînes qui commencent par un nombre, et, s'il y a du texte après, elle l'ignorent.

parseFloat("23.56px") //23.56

Pour tester si elles ont réussi, on utilisera la fonction isNaN().

    s="1234";
    i= parseInt(s);
    if (isNaN(i)) {
        console.log(s + " n'est pas un nombre");
    }

Notes: 1. On peut ajouter un paramètre de base. javascript parseInt("1101",2); //13 en binaire 2. On peut utiliser Number qui est moins permissif javascript Number("456.4"); Number("23px") 3. On peut (c mal ! ;-) utiliser les conversions automatiques liés à la multiplication * javascript "12345" * 1; // 12345

Les Objets

La structure de données de base est l'objet:

{} //Un objet vide
{ x : 1, y : 2 } //Un objet avec deux champs x et y.
o.x //Accès à un champ
o['x'] //Syntaxe alternative
o.z = 3; //rajoute le champ z à l'objet o !

Les [Objet standards natifs] (http://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects)

(Math,Date,etc.) # Boucle le retour...

Pour parcourir les indices d'un tableau, on peut utiliser for (let i in t)

   t= ["un", "deux", "trois"];
   for (let i in t) {
       console.log( "case "+i + " : " + t[i]);
   }

   t= ["un", "deux", "trois"];
   for(let x of t){
     console.log(x) //un deux trois
     }
  t= {"java": "langage de programmation orienté objet", 
      "lisp": "langage de programmation fonctionnel"};

  for (let clef in t) {
      console.log("information sur le langage " + clef + ": " + t[clef]);
  }

programmation asynchrone

Retour sur les fonctions

fonction pure vs "procedure"

fonction object de premiere classe

un nom de fonction est une variable comme une autre.

On peut définir des fonction anonymes

function next(){ let x=0 (function (){} }

On peut les passer en argument

Le DOM

L'object document et le DOM

L'objet global document représente le document HTML. Il implémente l'interface DOM et on peut donc le parcourir comme un arbre (propriétés firstChild, parent, nextSibling ...).

Éléments HTML

Les éléments HTML (document ou les objets renvoyés par getElementById) implémentent l'interface DOM du W3C (Document Object Model, une spécification pour faire correspondre des concepts HTML sur des langages Objets). Les méthodes qui vont nous intéresser pour le TP :

foo.addEventListener("event", f) : Exécute la fonction f quand l'évènement "event" se produit sur l'élément foo (ou un de ses descendants).

foo.innerHTML = "<b>Yo !</b>" : Remplace tout le contenu de l'élément foo par le fragment de document contenu dans la chaîne de caractère.

foo.value : Permet de modifier ou récupérer la valeur de certains éléments (en particulier les zones de saisies de texte). foo.style : Accès au style CSS de l'objet, représenté comme un objet Javascript

Évènements

Les navigateurs Web se programment de manière évènementielle : on attache des fonctions (event handlers) à des éléments. Quand un certain évènement se produit, la fonction est appelée :

      //On suppose qu'on a un évènement <div id="toto" >
      //dans notre HTML
      let divToto = document.getElementById("toto");
      function toggle_pink() {
         if (divToto.style.background == "") {
              divToto.style.background = "pink";
          } else {
              //s'il est déjà en rose, on l'efface et le
              //style global reprend le dessus
              divToto.style.background = "";
} }
toto.addEventListener("click", toggle_pink);

ATTENTION

Dans le slide précédant, il est bien écrit :

toto.addEventListener("click", toggle_pink); et non pas
toto.addEventListener("click", toggle_pink()); //ARCHI FAUX 

addEventListener attend deux arguments : le nom de l'évènement et une fonction. On appelle cette fonction un gestionnaire d'évènements

Quelques noms d'évènements

           let div = document.getElementById("mondiv");
           function clavier (e) {
               console.log("On a pressé la touche " + e.key);
           }
           div.addEventListener("keydown", clavier);

Modèle d'exécution

Répétitions

    let timer_id = setInterval(toggle_pink, 3000);
        //la fonction est appelée toutes les 3000 ms
           clearInterval(timer_id);
           //la fonction toggle_pink arrête d'être appelée

ATTENTION

Dans le slide précédant, il est bien écrit :

let timer_id = setInterval(toggle_pink, 3000); 

et non pas

let timer_id = setInterval(toggle_pink (), 3000);
  //TOUJOURS ARCHI FAUX

Modèle d'exécution (suite)

Débuggage : objet console

On peut utiliser `console.log(e)ù n'importe où dans du code pour afficher la valeur de l'expression e dans la console de débuggage Javascript.

Retour sur le programme

Dans l'ancien temps (2000 ?), les gens faisaient des choses comme ceci :

<html lang="fr">
  <head>...</head>
  <body>...
       <button id="b1" onclick="f()">Cliquez Moi</button>
       <input id="t2" type="text" oninput="x = 42; g(y);" />
        ...
    <body>
<html>          

--------- exemple de boucle dans un navigateur --------- x = prompt('dis-moi oui, humain!') while (x != 'oui') { x = prompt('dis-moi oui, humain!') } console.log('merci!')

PARTIE ENTREE SORTIE

Quelques methods d'affichage

Quelques methods de saisie.

const process = require('process');
  
// il faudrait évidement tester ne nombre d'arguments
console.log("chemin de node       ",process.argv[0]);
console.log("chemin du programme  ",process.argv[1]);
console.log("premier  argument    ",process.argv[2]);
console.log("second  argument     ",process.argv[2]);

et qu'on lance le programme dans un terminal en lui passant des argument

pons$ node exempleArgvNode.js argument1 argument2 ...

on a

chemin de node /usr/local/bin/node
chemin du programme /Users/pons/Documents/GIT/COURS/cours/NFA041/codefile/exempleArgvNode.js
premier  argument argument1
second  argument argument1
//le module prompt-sync doit être installé
//sinon  "npm install prompt-sync" le terminale terminal
```javascript
const prompt = require('prompt-sync')();

let nom = prompt('quel est votre nom?');
console.log("bonjour ",nom);
FIN DE LA PARTIE SPECIALE ENTREE/SORTIE

retour sur null et undefined

> let unevar   //declaration 
undefined
> unevar      //variable non initialisée vaut undefined
undefined
> unvar       // variable non déclarée ERREUR 
Uncaught ReferenceError: unvar is not defined