JavaScript : les bases

Michel Buffa
Avril 2012

buffa@unice.fr

Basics

Types primitifs, tableaux, boucles, formes conditionnelles...

Variables

Déclaration, initialisation

var a;
var ceciEstUneVariable;
var _et_ceci_aussi;
var mix12trois;
// invalide !
var 2three4five; // ne peut commencer par un chiffre !

var a = 1
var v1, v2, v3 = 'hello', v4 = 4, v5;

// Attentions aux majuscules/minuscules
var case_matters = 'lower';
var CASE_MATTERS = 'upper';
        

Utilisation de la console de debug JavaScript des navigateurs

Très pratiques pour tester la syntaxe JavaScript

Operateurs

>>> 1 + 2
3
>>> var a = 1;
>>> var b = 2;
>>> a + 1
2
>>> b + 2
4
>>> a + b
3
>>> var c = a + b;
>>> c
3
        

Opérateurs classiques

>>> 1 + 2
   3
>>> 99.99 – 11
   88.99
>>> 2 * 3
   6
>>> 6 / 4
   1.5

Modulo

>>> 6 % 3
  0
>>> 5 % 3
  2
>>> 4 % 2
    0
>>> 5 % 2
    1
        

Pré et post incrément

>>> var a = 123; var b = a++;
>>> b
   123
>>> a
   124
>>> var a = 123; var b = ++a;
>>> b
   124
>>> a
   124
>>> var a = 123; var b = a--;
>>> b
      123
>>> a
      122    

Ecritures courtes

>>> var a = 5;
>>> a += 3; // équivalent à a = a + 3;
   8
>>> a -= 3;
     5
>>> a *= 2;
    10
>>> a /= 5;
    2
>>> a %= 2; // équivalent à a = a % 2;
    0
    

Types primitifs

  • Number : 1, 100, 3.14
  • String : "a", "un", "un 2 trois"
  • Boolean : true ou false
  • Undefined : cas d'une variable non définie ou non initialisée (pas de valeur)
  • null : Cas d'une variable définie mais qui ne vaut rien.

Tout ce qui n'est pas d'un des types ci-dessus est un objet.

Trouver le type d'une variable : l'opérateur typeof

typeof : renvoie un objet de stype String, qui peut valoir "number", "string", "boolean", "undefined", "object", ou "function"

>>> var n = 1;
>>> typeof n;
   "number"
>>> n = 1234;
>>> typeof n;
   "number"
>>> var n2 = 1.23;
>>> typeof n;
    "number"
>>> typeof 123;
    "number"
    

Nombres en Octal ou en Hexadécimal

>>> var n3 = 0377;
>>> typeof n3;
    "number"
>>> n3;
    255
>>> var n4 = 0x00;
>>> typeof n4;
   "number"
>>> n4;
   0
>>> var n5 = 0xff;
>>> typeof n5;
    "number"
>>> n5;
    255
    

Nombre avec exposant

>>> 1e1
     10
>>> 1e+1
     10
>>> 2e+3
     2000
>>> typeof 2e+3;
     "number"
>>> 2e-3
    0.002
>>> 123.456E-3
    0.123456
>>> typeof 2e-3
  "number"
    

Valeurs infinies

>>> Infinity
   Infinity
>>> typeof Infinity
   "number"
>>> 1e309
   Infinity
>>> 1e308
   1e+308
>>> var a = 6 / 0;
>>> a
     Infinity
>>> var i = -Infinity;
>>> i
   -Infinity
>>> typeof i
   "number"

Infinity, suite...

>>> Infinity - Infinity
       NaN
>>> -Infinity + Infinity
       NaN

N'importe quelle opération avec Infinity donne Infinity comme résultat.

>>> Infinity - 20
       Infinity
>>> -Infinity * 3
       -Infinity
>>> Infinity / 2
       Infinity
>>> Infinity - 99999999999999999
       Infinity

Nan : Not a Number !

NaN est une valeur spéciale et son type est "Number" !

 >>> typeof NaN
      "number"
>>> var a = NaN;
>>> a
      NaN

NaN = résultat d'opérations arithmétiques impossibles :

>>> var a = 10 * "f";
>>> a
       NaN
>>> 1 + 2 + a
       NaN
    

Strings

Une chaine = entre simple ou double quotes "Hello" ou 'hello'...

>>> var s = "quelques caractères";
>>> typeof s;
       "string"
>>> var s = 'quelques caractères et des nombres 123 5.87';
>>> typeof s;
       "string"
>>> var s = '1';
>>> typeof s;
       "string"
>>> var s = ""; typeof s;
    "string"
    

Concaténation de chaines...

Comme en Java, l'opérateur "+" permet de concaténer des chaines...

>>> var s1 = "un"; var s2 = "deux"; var s = s1 + s2; s;
  "undeux"
>>> typeof s;
  "string"
    

Conversion de chaines de caractères...

Un nombre sous forme de String dans une expression arithmétique est converti en Number, sauf si la formule est une addition pure.

>>> var s = '1'; s = 3 * s; typeof s;
      "number"
>>> s
      3
>>> var s = '1'; s++; typeof s;
      "number"
>>> s
      2
>>> var s = "100"; typeof s;
     "string"
>>> s = s * 1;
     100
>>> typeof s;
     "number"
>>> var d = '101 dalmatiens';
>>> d * 1
     NaN
    

Conversion de nombre en String, astuce...

On concatène avec une chaine vide, en début d'expression

>>> var n = 1;
>>> typeof n;
      "number"
>>> n = "" + n;
      "1"
>>> typeof n;
      "string"
    

Caractère spéciaux, le "\" !

>>> var s = 'I don\'t know';
>>> var s = "I don\'t know";
>>> var s = "I don't know";
>>> var s = '"Hello", he said.';
>>> var s = "\"Hello\", he said.";
Escaping the escape:
>>> var s = "1\\2"; s;
       "1\2"
>>> var s = '\n1\n2\n3\n';
>>> s
       "
       1
       2
       3
       "
    

Caractère spéciaux, suite...

>>> var s = '1\r2';
>>> var s = '1\n\r2';
>>> var s = '1\r\n2';
// les trois donnent :
>>> s
       "1
       2"
// tabulation
>>> var s = "1\t2"
>>> s
       "1    2"
// Ecrire en bulgare :
>>> "\u0421\u0442\u043E\u044F\u043D"
       "Стoян"
    

Booléens

>>> var b = true; typeof b;
    "boolean"
>>> var b = false; typeof b;
    "boolean"        
    

Mais... si on met entre quotes :

>>> var b = "true"; typeof b;
"string"
    

Opérateurs logiques

Ce sont les mêmes qu'en java : && (ET), || (OU), ! (NOT)

>>> var b = !true;
>>> b;
       false
>>> var b = !!true;
>>> b;
       true
>>> var b = "one";
>>> !b;
       false
>>> var b = "one";
>>> !!b;
       true

    

Conversion implicite de booléens

Dans une expression booléene avec des opérateurs logiques, les valeurs non booléennes sont converties implicitement.

0, null, Undefined, false, NaN, la chaine vide "" sont converties en false

Toute le reste est converti en true

Astuce pour convertir en booléen :

!!a 

Précédence des opérateurs

* avant + et -

>>> 1 + 2 * 3
   7
>>> 1 + (2 * 3)
   7

! avant && et ||

>>> false && false || true && true
  true
>>> (false && false) || (true && true) 
  true 

Conseil : utilisez des parenthèses et tout ira bien !

Evaluation fainéante (lazy evaluation)

>>> (false && false) || (true && true)
  true
>>> var b = 5;
>>> true || (b = 6)   // la deuxième partie jamais évaluée !
   true
>>> b
    5
>>> true && (b = 6)   // deuxième partie évaluée
    6
>>> b
    6

Particularité de JavaScript

Si une opérande non booléene est rencontrée dans une expression, c'est cette dernière qui est renvoyée

>>> true || "something" 
    true

>>> true && "something" 
    "something"

Opérateurs de comparaison

Symbole Description Exemple
== Egalité avec conversion de type
>>> 1 == 1
   true
>>> 1 == 2
   false
>>> 1 == '1'
   true
=== Egalité sans conversion de type
>>> 1 === '1'
  false
>>> 1 === 1
  true

Opérateurs de comparaison

Symbole Description Exemple
!= Non égalité avec conversion de type
>>> 1 != 1
   false
>>> 1 != '1'
   false
>>> 1 != '2'
   true
!== Non égalité sans conversion de type
>>> 1 !== 1
  false
>>> 1 !== '1'
  true

Cas particulier de NaN

NaN n'est égal avec RIEN ! Même pas lui-même !

>>> NaN == NaN
    false
    

Undefined et null

Undefined est retourné lorsque on accède à une variable qui n'existe pas :

>>> foo
     foo is not defined
>>> typeof foo
     "undefined"

Ou à une variable qui n'est pas initialisée :

>>> var somevar; 
>>> somevar 
>>> typeof somevar 
       "undefined" 
    

Undefined et null

null n'est jamais positionné par JavaScript, uniquement par le code :

>>> var somevar = null
       null
>>> somevar
       null
>>> typeof somevar
       "object"
    

Différences entre undefined et null

>>> var i = 1 + undefined; i;
      NaN
>>> var i = 1 + null; i;
      1
    

Cela vient de la conversion implicite de null et undefined dans une expression

>>> 1*undefined
       NaN
>>> 1*null
       0

Différences entre undefined et null

Conversion en booléens : les deux valent false:

>>> !!undefined
      false 
>>> !!null
      false
    

Conversion vers String :

>>> "" + null
       "null"
>>> "" + undefined
       "undefined"
    

Tableaux

Les tableaux sont des objets !

>>> var a = [];
>>> typeof a;
       "object"
>>> var a = [1,2,3];
>>> a
      [1, 2, 3]
>>> a[0]
    1
>>> a[1]
    2
    

Tableaux : ajout/modification d'éléments

>>> var a = [1,2,3];
>>> a[2] = 'three'; 
    "three" 
>>> a 
    [1, 2, "three"]
    

On peut ajouter de nouveaux éléments en utilisant un nouvel index :

>>> a[3] = 'four';
    "four"
>>> a
    [1, 2, "three", "four"]
    

Ajout d'éléments en laissant des trous dans les indexs :

Si on ajoute des éléments avec des indexs numériques non contigus, les valeurs manquantes sont undefined :

>>> var a = [1,2,3];
>>> a[6] = 'new';
       "new"
>>> a
       [1, 2, 3, undefined, undefined, undefined, "new"]
    

Tableau : suppression d'éléments

On supprime des éléments à l'aide de l'opérateur delete.

L'élément n'est pas vraiment supprimé mais sa valeur devient undefined.

La taille du tableau ne change pas.

>>> var a = [1, 2, 3];
>>> delete a[1];
   true
>>> a
   [1, undefined, 3]
    

Tableaux de tableaux

Un tableau peut contenir n'importe quel type d'éléments, y compris un autre tableau :

>>> var a = [1, "two", false, null, undefined];
>>> a
    [1, "two", false, null, undefined]
>>> a[5] = [1,2,3]
    [1, 2, 3]
>>> a
    [1, "two", false, null, undefined, [1, 2, 3]]
    

Tableaux de tableaux : exemples

>>> var a = [[1,2,3],[4,5,6]];
>>> a
      [[1, 2, 3], [4, 5, 6]]
>>> a[0] 
       [1, 2, 3]
    

Accès à un élément d'un tableau de tableau :

>>> a[0][0]
       1
>>> a[1][2]
       6
    

Tableaux et Strings

Une String se manipule comme un tableau de caractères :

>>> var s = 'one';
>>> s[0]
       "o"
>>> s[1]
       "n"
>>> s[2]
       "e"
    

Formes conditionnelles et boucles

Les conditions permettent de controller le flux d'exécution,

Les boucles permettent de répéter des blocs d'instructions,

Les mots-clés JavaScript sont : if, switch, while, do-while, for, et for-in.

Bloc de code : un bloc de code contient plusieurs expressions entre { et } :

{
  var a = 1;
  var b = 3;
}
{
  var a = 1;
  var b = 3;
  var c, d;
  {
    c = a + b; // Bloc de code imbriqué dans un autre bloc de code
    {
      d = a - b;
    }
  }
}

Exemple de bloc conditionnel :

var resultat = '';
if (a > 2) {
  resultat = 'a est plus grand que 2';
}
    

La conditon peut comprendre :

  • Des opérateurs logiques !, && et ||
  • Des opérateurs de comparaison : != == > >= < <= etc.
  • N'importe quelle valeur ou expression qui peut être converti en booléen

Blocs conditionnel avec else

if (a > 2) {
  resultat = 'a est plus grand que 2';
} else {
  resultat = 'a n'est PAS plus grand que 2';
}
    
if (a > 2 || a < -2) {
  resultat = "a n'est pas compris entre -2 et 2";
} else if (a === 0 && b === 0) {
  resultat = 'a et b sont nuls';
} else if (a === b) {
  resultat = 'a et b sont égaux';
} else {
  resultat = "J'abandonne";
}
    

Blocs conditionnels imbriqués

if (a === 1) {
  if (b === 2) {
    resultat = 'a vaut 1 et b vaut 2';
  } else {
    resultat = 'a vaut 1 mais b est différent de 2';
  }
} else {
  resultat = 'a est différent de 1, je ne sais pas pour b';
}
    

Tester si une variable existe

On peut tester avec if(variable)... :

>>> var resultat = ''; 
>>> if (unevariable){resultat = 'oui';}
       unevariable is not defined
>>> resultat;
       ""
    

On peut faire plus propre avec l'opérateur typeof :

>>> if (typeof unevariable !== "undefined"){resultat = 'oui';}
>>> resultat;
       ""
    

Tester si une variable existe avec typeof

typeof renvoie toujours une String

>>> unevariable = 123;
>>> if (typeof unevariable !== "undefined"){resultat = 'oui';}
>>> resultat;
       "oui"
    

Bloc conditionnel, syntaxe alternative

var resultat = (a === 1) ? "a vaut 1" : "a est différent de 1";
    

Est équivalent à :

var a = 1;
var resultat = '';
if (a === 1) {
  resultat = "a vaut 1";
} else {
  resultat = "a est différent de 1";
}
    

Switch... case

Remplace un if avec plusieurs else...

var a = '1';
var resultat = '';
switch (a) {
  case 1:
    resultat = 'Nombre 1';
    break;
  case '1':
    resultat = 'String 1'; // Résultat pour cet exemple
    break;
  default:
    resultat = 'Je ne sais pas';
    break;
}
resultat;
    

Boucles

While... (boucles "tant que...")

var i = 0;
while (i < 10) {
  i++;
}        
    

Do... While (boucles "jusqu'à"...)

var i = 0;
do {
  i++;
} while (i < 10)
    

Boucles

For (boucle "pour"...)

var punition = '';
for (var i = 0; i < 100; i++) {
  punition += 'Je ne copierai plus sur mon voisin,\n';
}
    
for (var i = 0, punition = ''; i < 100; i++) {
  punition += 'Je ne copierai plus sur mon voisin,\n';
}

Boucles imbriquées

var res = '\n';
for(var i = 0; i < 4; i++) {
  for(var j = 0; j < 10; j++) {
    res += '* ';
  }
  res+= '\n';
}
    
Affiche :
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
    

Boucles for...in

Ces boucles servent à itérer sur les éléments d'un tableau (ou d'un objet, nous verrons cela plus tard)

var a = ['a', 'b', 'c', 'x'];
var result = '\n';
for (var i in a) {
  result += 'index: ' + i + ', value: ' + a[i] + '\n';
}        
    

Résultat :

index: 0, value: a
index: 1, value: b
index: 2, value: c
index: 3, value: x
    

Commentaires dans le code

Similaires à Java : à l'aide de // ou de /*......*/

// début de ligne
var a = 1; // ailleurs sur une ligne
/*
    Un commentaire
    sur plusieurs lignes
 */
var b = 2;
    

Fonctions...

Une fonction : définition

Une fonction permet de regrouper du code, de lui donner un nom et de pouvoir l'exécuter en l'appelant par son nom.

function somme(a, b) {
    var c = a + b;
    return c;
}        
    

Appel de la fonction :

>>> var result = somme(1, 2);
>>> result;
       3
    

Fonctions et paramètres

Si on omet des paramètres, JavaScript leur donne la valeur undefined :

>>> somme(1)
       NaN        
    

Si on passe trop de paramètres, certains seront ignorés :

>>> somme(1, 2, 3, 4, 5)
       3        
    

Fonctions avec un nombre de paramètres variable

Un tableau de nom arguments est créé automatiquement dans chaque fonction,

Il contient l'ensemble des paramètres d'appel de la fonction :

>>> function f() { return arguments; }
>>> f();
       []
>>> f( 1, 2, 3, 4, true, 'ninja');
       [1, 2, 3, 4, true, "ninja"]
    

Exemple de la somme avec un nombre variable d'arguments

function sommeAmelioree() {
  var i, res = 0;
  var nombreDeParametres = arguments.length;
  for (i = 0; i < nombreDeParametres; i++) {
    res += arguments[i];
  }
  return res;
}
    
>>> sommeAmelioree(1, 1, 1);
       3
>>> sommeAmelioree(1, 2, 3, 4);
      10
    

Fonctions prédéfinies

Il existe un certain nombre de fonctions prédéfinies en JavaScript

  • parseInt()
  • parseFloat()
  • isNaN()
  • isFinite()
  • encodeURI()
  • decodeURI()
  • encodeURIComponent()
  • decodeURIComponent()
  • eval()

parseInt()

Conversion en entier

>>> parseInt('123')
       123
>>> parseInt('abc123')
       NaN
>>> parseInt('1abc23') // attention !
       1
>>> parseInt('123abc') // attention !
       123
    

Note : il est possible de passer la base en second paramètre...10, 16 ou 8.

parseFloat()

Conversion en flottant

>>> parseFloat('123')
       123
>>> parseFloat('1.23')
       1.23
>>> parseFloat('1.23abc.00')
       1.23
>>> parseFloat('a.bc1.23')
       NaN
>>> parseFloat('123e-2')
       1.23
>>> parseFloat('123e2')
       12300
>>> parseInt('1e10')
       1    

isNaN

Permet de tester si une valeur est NaN

>>> isNaN(NaN)
       true
>>> isNaN(123)
       false
>>> isNaN(1.23)
       false
>>> isNaN(parseInt('abc123'))
       true
    

isFinite()

Vérifie si le paramètre est différent de Infinity ou de NaN

>>> isFinite(Infinity)
  false
>>> isFinite(-Infinity)
  false
>>> isFinite(12)
  true
>>> isFinite(1e308)
  true
>>> isFinite(1e309) // trop grand !
  false        
    

Plus grand nombre en JavaScript = 1.7976931348623157e+308 !

encodeURI(), encodeURIComponent(), decodeURI(), decodeURIComponent(), escape(), unescape()

Ces fonctions permettent d'échapper certains caractères dans un URI

encodeURI() et son inverse decodeURI() pour les espaces, accents, etc.

>>> var url = 'http://www.packtpub.com/scr ipt.php?q=this and that';
>>> encodeURI(url);
       "http://www.packtpub.com/scr%20ipt.php?q=this%20and%20that"
        

encodeURIComponent() et decodeURIComponent() pour les :// etc.

>>> encodeURIComponent(url);
       "http%3A%2F%2Fwww.packtpub.com%2Fscr%20ipt.php%3Fq%3Dthis%
       20and%20that"
    

Ces fonctions remplacent escape() et unescape() qui sont deprecated.

eval()

eval() : évaluer du JavaScript passé en paramètre :

>>> eval('var i = 2;')
>>> i
       2        
    

Attention avec eval() :

  • Comment debuger le code évalué ?
  • Performance : il est plus lent d'évaluer du code que de l'exécuter normalement,
  • Sécurité : confiance dans le code évalué ?
  • Pratique pour faire un tutorial JavaScript en ligne !

alert()

Fenêtre de dialogue dans le navigateur avec un message.

alert("hello!")
    

Bloque le thread de gestion de la GUI du browser. Eviter avec Ajax ! Utiliser la console de debug JavaScript à la place !

Portée des variables : attention, différent des autres langages !

JavaScript est très particulier, bien noter ces points :

  • Ce qui limite la portée d'une variable c'est la fonction, pas le bloc entre { et }
  • Une variable définie dans une fonction ne sera pas visible à l'extérieur de cette fonction,
  • Une variable définie dans un if ou un for sera visible à l'extérieur du bloc,
  • Variable globale = en dehors d'une fonction,
  • Variable locale = dans une fonction

Portée des variables : exemples...

var global = 1;
function f() {
  var local = 2;
  global++;
  return global;
}
>>> f();
     2
>>> f();
     3
>>> local
  local is not defined
    

Le mot clé var, attention au piège !

Une variable non définie avec var est automatiquement considérée comme globale !

Lors de la définition de la fonction, local n'existe pas,

Lors du premier appel elle est définie en global, ensuite elle existe... Toujours déclarer avec var !

Encore un piège ! Global versus Local

var a = 123;
function f() { 
  alert(a);
  var a = 1;
  alert(a); 
} 
f();         
    

Le premier alert() affiche Undefined et pas 123 !

En effet, le fait qu'une variable a locale soit définie dans la fonction, même après le alert(), cache le fait qu'il existe un a global, comme le a local n'est pas encore initialisé et déclaré, cela provoque un Undefined !

Les fonctions sont des données !

Il s'agit d'un concept très important ! Ces deux écritures sont équivalentes :

function f(){return 1;}
var f = function(){return 1;} // déclaration littérale
>>> typeof f
       "function"
    

Oui, function est un type de donnée et f une variable de ce type.

Si f est une variable de type fonction on peut exécuter son code an ajoutant (...); à son nom.

Les fonctions sont des données : exemple !

>>> var somme = function(a, b) {return a + b;}
>>> var addition = somme;
>>> delete somme
       true
>>> typeof somme;
       "undefined"
>>> typeof addition;
       "function"
>>> addition(1, 2);
       3
    

Puisque une variable peut être une fonction, on appliquera les mêmes règles de nommage (débute par un caractère, peut contenir chiffres et underscore).

Fonctions anonymes

On peut écrire du code en dehors de fonctions :

>>> "test"; [1,2,3]; undefined; null; 1;         
    

On peut aussi définir des fonctions sans nom :

>>> function(a){return a;}        
    

Intérêt : on peut les passer en paramètres à la manière des écouteurs Java.

En JavaScript ces écouteurs s'appellent des callbacks et ils sont omniprésents dans les applications web.

Callbacks !

function invoke_and_add(a, b){
  return a() + b(); // les paramètres sont des fonctions !
}
function one() {
  return 1;
}
function two() {
  return 2;
}
>>> invoke_and_add(one, two); // deux fonctions en paramètres !
   3
>>> invoke_and_add(function(){return 1;}, function(){return 2;});
   3
    

Le dernier exemple utilise des fonctions de callback anonymes (sans noms) !

Encore un exemple de callbacks...

Etape 1 : définissons deux fonctions :

function multiplieParDeux(a, b, c) {
  var i, ar = [];
  for(i = 0; i < 3; i++) {
    ar[i] = arguments[i] * 2;
  }
  return ar;
}
function ajouteUn(a) {
  return a + 1;
}
>>> multiplyByTwo(1, 2, 3);     // renvoie un tableau
       [2, 4, 6]
>>> addOne(100)
       101
    

Suite de l'exemple...

>>> var myarr = [];
>>> myarr = multiplieParDeux(10, 20, 30);
       [20, 40, 60]
        
// On ajoute un à chaque élément maintenant :
>>> for (var i = 0; i < 3; i++) {myarr[i] = ajouteUn(myarr[i]);}
>>> myarr
       [21, 41, 61]
    

On voudrait tout faire dans la même fonction, mais en pouvant choisir le type d'opération qu'on fait après avoir multiplié tous les éléments par 2 !

Suite de l'exemple...

function multiplieParDeux(a, b, c, callback) {
  var i, ar = [];
  for(i = 0; i < 3; i++) {
    ar[i] = callback(arguments[i] * 2);
  }
  return ar;
}
>>> myarr = multiplieParDeux(1, 2, 3, ajouteUn);
       [3, 5, 7]
>>> myarr = multiplieParDeux(1, 2, 3, function(a){return a + 1});
       [3, 5, 7]
>>> myarr = multiplieParDeux(1, 2, 3, function(a){return a + 2});
       [4, 6, 8]
    

Fonctions auto-appelantes (self invoking functions)

Attention à la syntaxe !

(
  function(){
    alert('boo');
  }
)();
    
(
  function(nom){
    alert('Hello ' + nom + '!');
  }
)('Michel');
    

Fonctions auto-appelantes (self invoking functions)

Utile pour une initialisations, puisqu'on ne peut les appeler qu'une fois avec des paramètres prédéfinis.

Evite l'utilisation de variables globales.

Présenté ici car utilisé par certaines librairies, par des closures (étudiées plus loin)...

Font partie des horreurs de JavaScript qui font hurler les puristes. Cool donc ;-)

Fonctions "internes" ou privées

function a(param1) {
  function b(param2) {
    return param2 * 2;
  };
  return 'Le résultat est ' + b(param1);
};
// Peut s'écrire aussi en forme littérale
var a = function(param1) {
  var b = function(param2) {
    return theinput * 2;
  };
  return 'Le résultat est ' + b(param1);
};
>>> a(8);
      "Le résultat est 16"
>>> b(2);
       b is not defined
    

Fonctions internes ou privées : intérêt...

Permet de n'exposer que quelques fonctions, les autres sont "cachées"

Evite les collisions de noms

Très utile quand on fera du javaScript orienté objet (on pourra écrire a.b(); !), une fonction interne sera considérée comme une méthode !

Fonctions qui renvoient des fonctions

Les fonctions étant une donnée comme une autre, elle peut être une valeur de retour :

function a() {
  alert('A!');
  return function(){
    alert('B!');
  };
}>>> var f = a();
>>> f(); // fera pparaitre un popup avec 'B' !

>>> a()(); // fait la même chose ! 
    

a() invoque la fonction a, et a()() invoque la fonction retournée par a()

Exemple un peu plus compliqué...

var a = function() {
  function someSetup(){
    var setup = 'done';
  }
  function actualWork() {
    alert('Worky-worky');
  }
  someSetup();
  return actualWork;
}();
    

Fonction auto invoquée ! a recevra le résultat !

La fonction appelle someSetup() qui peut effectuer des inits ou des tests, par exemple tester dans quel navigateur elle s'exécute...

Elle revoie finalement une fonction (actualWork)... c'est cette valeur que recevra a,

Si on appelle a(); c'est actualWork() qui sera appelée !

Ah... les closures ! Ah....Hmmm...

Les closures

  • Les closures sont en général difficiles à comprendre quand on les aborde pour la première fois,
  • Ne vous découragez pas ! Un jour comme ça finit par rentrer !
  • Nécessite de tester des exemples par vous même, par exemple dans l'IDE en ligne JSbin.com
  • Nécessite d'avoir eu des problèmes liés aux closures pour s'y mettre vraiment !
  • Voir par exemple ce cas à priori simple dans le tutorial HTML5 de M.Buffa
  • Avant d'aller plus loin, complètons ce que nous avons vu sur la portée des variables.

Rappels sur la portée des variables

Pas de portée par "bloc" mais par "fonction" !

>>> var a = 1; function f(){var b = 1; return a;}
>>> f();
       1
>>> b
       b is not defined        
    

Dans f() a et b sont visibles,

En dehors de f() seul a est visible.

Cas des fonctions internes

var a = 1; 
function f(){
  var b = 1; 
  function n() {
    var c = 3;
  }
}        
    

n() est interne à f(),

n() voit les variables de sa portée et aussi celle de son parent. Elle voit donc c, mais aussi b et a.

On parle de "chaîne de portée" ou en anglais "scope chain".

Portées "lexicales" : créées à la définition des fonctions

>>> function f1(){var a = 1; f2();}
>>> function f2(){return a;}
>>> f1();
       a is not defined        
    

Dans cet exemple, f2() ne voit que son scope et le scope global, a n'est pas définie dedans.

Pourtant lors de l'appel de f2() par f1(), a est bien définie.

Continuons avec les portées de fonctions...

Une portée/scope: ensemble de "chemins" dans lesquels des données seront visibles

Définis à la création, non modifiables, mais on peut ajouter/enlever des données dans ces chemins à l'exécution.

>>> function f1(){var a = 1; return f2();}
>>> function f2(){return a;}
>>> f1();
    a is not defined
>>> var a = 5;
>>> f1();
       5
>>> a = 55;
>>> f1();
       55
>>> delete a;
       true
>>> f1();
       a is not defined
    

Exemple précédent, suite...

f2() est dans le scope de f1(), mais on peut modifier la valeur de f2 ou la supprimer.

>>> delete f2;
    true
>>> f1()
    f2 is not defined
>>> var f2 = function(){return a * 2;}
>>> var a = 5;
    5
>>> f1();
    10
    

Bon, mais et les closures ? Et bien elles servent à "casser" la chaine des portées, la "scope chain" !

Casser la chaine de portée avec une closure (Aïe!)

Imaginons le "global scope" comme l'univers. Il est visible par tous !

Il contient des variables et des fonctions (les a1, a2) et des fonctions (F)

Casser la chaine de portée avec une closure, suite...

Les fonctions ont leur propre scope avec variables et fonctions

si F() contient N()...

Le point a est dans le scope global, b dans le scope de F, c dans le scope de N, etc.

De a on ne peut voir b, mais de c on peut voir b, donc de N on peut voir b.

Closure : mettre N dans le scope global !

... mais sans le sortir réellement !

N est au même endoit que a maintenant,

Il voit encore le scope de F donc il voit b, alors que a ne le voit pas !

Comment sortir N de F... sans le sortir réellement ?

Deux possibilités :

  1. En ne mettant pas le mot clé var devant la définition de la fonction N,
  2. En faisant en sorte que F retourne N, si F est appelée depuis l'espace global, la valeur retournée (N) devient ainsi visible dans cet espace.

Regardons quelques exemples ensembles...

Closure, exemple 1

function f(){
  var b = "b";
  return function(){
    return b;
  }
}
>>> b
       b is not defined
>>> var n = f();
>>> n();
       "b"
    

La fonction retournée est similaire à la fonction N des transparents précédents.

Closure, exemple 2

var n;
function f(){
  var b = "b";
  n = function(){ // pas de var !
    return b;
  }
}
>>> f();
>>> n();
       "b"
    

L'appel de f() a défini une nouvelle variable n dans le scope global. Cette variable est une fonction qui voit b.

Closure, exemple 3

function f(arg) {
  var n = function(){
    return arg;
  };
  arg++;
  return n;
}
>>> var resultat = f(123);
>>> resultat();
       124
    

arg++ est appelé après la définition de n, pourtant l'appel de resultat() donne bien la valeur à jour incrémentée.

Ceci montre bien que les fonctions sont liées à un scope et pas à des valeurs de variables. Lorsque les variables changent dans le scope, ce sont toujours les valeurs à jour qui sont utilisées.

Closures et boucles : le piège archi-classique !

C'est celui de cet exemple simple dans le tutorial HTML5 de M.Buffa

function f() {
  var a = [];
  var i; 
  for(i = 0; i < 3; i++) {
    a[i] = function(){
      return i;
    }
  } 
  return a;
}        
    

Que donne l'exécution de cette fonction ?

Reprenons cet exemple et exécutons-le !

function f() {
  var a = [];
  var i;
  for(i = 0; i < 3; i++) {
    a[i] = function(){
      return i;
    }
  }
  return a;
}
>>> var a = f();
>>> a[0]()
  3
>>> a[1]()
  3
>>> a[2]()
  3
    

Mais ? Mais ? Pourquoi ???

Reprenons tranquillement le code en gardant à l'esprit ce que nous venons d'apprendre...

  • La fonction f() définit un tableau, et dans une boucle exécutée trois fois, on remplit trois éléments de ce tableau,
  • Ce que l'on met dans chaque élément est une fonction anonyme qui renvoit la valeur de i,
  • On renvoit le tableau qui contient ces trois fonctions, donc on "sort" à travers ce tableau trois fonctions pour les rendre visible dans l'espace global,
  • C'est une Closure !

Suite des explications...

  • les trois éléments du tableau a sont des fonctions identiques qui voient i puisque i est dans leur scope,
  • Si on execute a[0](); ou a[1](); ou a[2](); on exécute le même code,
  • Ce code renvoie la valeur de i, la valeur à jour ! Comme f() a déjà été exécutée, i a déjà été incrémenté trois fois, sa valeur à jour est 3, ce qui explique le résultat !
  • Alors , comment faire ?

     function f() {
      var a = [];
      var i;
      for(i = 0; i < 3; i++) {
        a[i] = (function(x){
          return function(){
            return x;
          }
        })(i);
      }
      return a;
    }
    >>> var a = f();
    >>> a[0]();
         0
    >>> a[1]();
          1
    >>> a[2]();
          2
        

    Explications...

    On met dans a[i] une fonction auto-exécutée :

        a[i] = (function(x){
          return function(){
            return x;
          }
        })(i);       
        

    Cette fonction prend comme argument i, qui devient le paramètre x, local à la fonction,

    a[0] reçoit donc une fonction qui a pour paramètre 0, a[1] une fonction qui a pour paramètre 1, etc.

    Autre version possible...

    function f() {
      function makeClosure(x) {
        return function(){
          return x;
        }
      }
      var a = [];
      var i;
      for(i = 0; i < 3; i++) {
        a[i] = makeClosure(i);
      }
      return a;
    }        
        

    Ca marche ou pas ?

    Cas classique : un itérateur avec des closures

    function setup(x) {
      var i = 0;
      return function(){
        return x[i++];
      };
    }
    >>> var next = setup(['a', 'b', 'c']);
    >>> next();
         "a"
    >>> next();
        "b"
    >>> next();
        "c"