Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Javascript: closures et scope

Poster un commentaire

Impossible de comprendre nombre de patterns JavaScript sans maîtrise des closures (clôture ou fermeture) et des scopes (portée).

Je propose de faire le point dans cet article sur ces deux notions intimement liées. C’est une étape indispensable pour passer du niveau 100 au niveau 200 en JavaScript.

On parle développement, il est donc indispensable de tester le code, pas juste le lire. Personnellement je vous propose cet outil en ligne. C’est pas le plus beau, mais il est simple.

Le scope

La portée, le scope, se réfère à l’endroit d’où une variable est accessible.

Une variable ou une fonction peuvent être définies dans le scope global ou dans le scope local.

Le scope global signifie accessible depuis n’importe où dans le code.

var message = ‘je suis le plus fort’;
function chanter() {alert(‘une chanson douce’);};

Dans cet exemple, la variable et la fonction sont dans le scope global. On peut les appeler de n’importe où dans le code JavaScript.

Le scope local signifie que quelque chose n’est accessible que depuis une partie du code.

function chanter() {
         var nomChanson = ‘une chanson douce’;
         alert(nomChanson);
}

alert(nomChanson); // ne marchera pas

Dans cet exemple le scope de la variable nomChanson est le corps de la fonction chanter(). La ligne avec le deuxième alert affichera undefined tandis que le alert dans le corps de la fonction marche comme prévu.

Nous préciserons cela avec la notion de closure qui est une façon de compléter la notion de scope.

Pour l’instant retenez qu’en JavaScript, un bloc de code ne définit pas un scope. La seule façon de le faire est de créer une fonction. Le code suivant fonctionne très bien par exemple:

var chanter=1;
if (chanter == 1) {    
       var nomChanson=’une chanson douce’;
}

alert(nomChanson); // affiche le nom de la fonction

Les closures

Le plus simple est de comparer deux exemples:

function testClosure()

     var date = new Date(); 
     return date.getMilliseconds();
}

alert(typeof date) // undefined

date est une variable définie dans une fonction. Comme le démontre cet exemple, date n’existe plus en dehors du corps de la fonction.
Le fait d’appeler ou non testClosure ne change rien à l’affaire:

function testClosure()

var date = new Date(); 

return date.getMilliseconds();

}

var retour = testClosure();
alert(typeof date) // undefined

On pourrait inverser la situation:

var msg= »hello »;

function testClosure() {
var display = msg +  » Amethyste »;
return display;
}

var retour = testClosure();
alert(retour) // hello Amethyste

La variable msg est définie en dehors de la fonction et est accessible y compris dans le corps de celle-ci.
Cela marche même si on définit msg après la fonction:

function testClosure() {
var display = msg +  » Amethyste »;
return display;
}

var msg= »hello »;
var retour = testClosure();
alert(retour) // hello Amethyste

En JavaScript le scope d’une fonction est établit par sa position dans le code. Une fonction a toujours accès aux variables déclarées dans son scope courant et dans son scope externe. Mais pas l’inverse.

Cette relation s’appelle une closure. Notez bien l’asymétrie de la situation, tout est là.

Examinons deux cas de figure importants:

function testClosure() {  
var msg = ‘hello’;      

//expression de fonction imbriquée  
(function() {   
                     alert(msg + ‘ Amethyste’);  
                       })();
};

testClosure(); // hello Amethyste

testClosure établit une closure. Puisque l’expression de fonction est à l’intérieur de testClosure, la variable date est dans son scope. Elle peut donc accéder à cette variable.

Notez le pattern avec lequel l’expression de fonction est exécutée. Il a fait l’objet d’un article précédent.

Le piège classique des closures est celui-ci:

function display(msg) {alert(msg);}

 var helpText = [‘1′,’2′,’3′,’4′,’5′,’6′,’7′,’8′,’9′,’10’];

function boucle() {
        for (var i = 0; i < helpText.length; i++)   
                   setTimeout(function () {display(i + « : » + helpText[i])},1000); };

boucle();

A priori on s’attend à voir s’afficher une suite de messages du genre:

0:1
1:2
2:3

On ne voit pourtant s’afficher que des 0:undefined.

Que se passe t’il?

SetTimeout attend 1 secondes pour exécuter l’expression. Lors du premier appel effectivement item contient ce à quoi on s’attend. Seulement lorsque l’expression s’exécute la boucle a eu largement le temps de s’exécuter et la référence a changée depuis longtemps. La variable i a atteint son maximum, 10, et c’est avec cette valeur que helpText[i] a été évalué, ce qui retourne ’10’.
Puisque helpText n’a pas d’item à l’index 10, on récupère undefined.

Cela explique donc l’affichage observé.

Pour résoudre ce problème on aurait donc besoin d’isoler notre variable i du contexte global. Il faut la placer dans une closure. Ainsi le contexte global dans lequel tourne la boucle ne pourra pas modifier la valeur de i, une fois écoulée la seconde d’attente.

Nous avons vu qu’une closure est JavaScript est définie par une fonction. Nous allons donc créer une fonction.

function display(msg) {alert(msg);}

var helpText = [‘1′,’2′,’3′,’4′,’5′,’6′,’7′,’8′,’9′,’10’];

function boucle() {
         for (var i = 0; i < helpText.length; i++)   
                 (function() {var j=i; setTimeout(function () {display(j + « : » + helpText[j])}, 1000);})();
};

boucle();

La fonction anonyme créée une closure. Cela signifie qu’elle a bien accès à i, mais par contre la valeur de i ne peut par la suite être modifiée une fois que la boucle continue son exécution.

De fait l’affichage est bien celui attendu.

La variable intermédiaire j ne joue pas d’autres rôle que rendre le script plus clair. Mais ceci marche aussi:

function display(msg) {alert(msg);}

var helpText = [‘1′,’2′,’3′,’4′,’5′,’6′,’7′,’8′,’9′,’10’];

function boucle() {
for (var i = 0; i < helpText.length; i++)
(function(i) {setTimeout(function () {display(i+ « : » + helpText[i])}, 1000);})(i);
};

boucle();

Une closure a beaucoup d’usages importants comme la création d’espace de noms en JavaScript, de variables privées… Nous reverrons cela dans d’autres articles à suivre.

En attendant vous pouvez lire cet article ou celui-ci.

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s