En Javascript, il est important de maîtriser le scope des variables pour éviter les effets de bord et maîtriser l'utilisation de closures.
Voici un rapide résumé des règles du scope des variables globales :
- une variable est une variable globale si elle déclarée pour la première fois sans utiliser le mot clé
var
, que l'on soit ou non dans le code d'une fonction - une variable globale est accessible et modifiable de partout
Voici un rapide résumé des règles du scope des variables d'une fonction :
- une variable est dans le scope d'une fonction si cette variable a été déclarée via le mot clé
var
- le paramètre d'une fonction est dans le scope des variables de cette fonction : il s'agit du même comportement que pour une variable définie via le mot clé
var
- une variable dans le scope d'une fonction est accessible et modifiable depuis le code de cette fonction ainsi que depuis les fonctions enfants de cette fonction
- une variable du scope d'une fonction parent peut être masquée dans une fonction enfant par une variable de même nom dans le scope de cette fonction enfant
Règle de survie en Javascript :
Le comportement des variables globales est dangereux et provoque des effets de bord très difficiles à corriger, c'est pourquoi il faut toujours utiliser le mot clé var
pour déclarer une variable.
Variable globale VS Variable dans le scope d'une fonction
Par défaut, une variable est globale, c'est à dire qu'elle est accessible et modifiable de partout.Le mot clé
var
indique que la variable est dans le scope de la fonction dans laquelle elle a été déclarée.console.log('a', typeof a === 'undefined'); // true : a n'est pas défini console.log('b', typeof b === 'undefined'); // true : b n'est pas défini function test() { var a = 1; // a est une variable dans le scope de la fonction "test" b = 2; // b devient une variable globale } test(); // a n'est plus accessible car on est sorti de la fonction "test" console.log('a', typeof a === 'undefined'); // true : a n'est pas défini // b est une variable globale console.log('b', typeof b === 'undefined'); // false : b est défini : b = 2 console.log('b', b); // b = 2
La variable
a
a été définie via le mot clé var
et est donc une variable définie dans le scope de la function test
. La variable a
n'est donc pas accessible à l'extérieur de la fonction test
où elle a été définie pour la première fois.Cependant
b
est devenu une variable globale car elle a été définie pour la première fois dans la fonction sans utiliser le mot clé var
. Par conséquent, elle reste définie et accessible à l'extérieur de la fonction test
.Je répète la règle suivante pour éviter les effets de bord en Javascript :
Le comportement des variables globales est dangereux et provoque des effets de bord très difficiles à corriger, c'est pourquoi il faut toujours utiliser le mot clé var
pour déclarer une variable.
Héritage des scopes des fonctions parents
Une fonction déclarée au sein d'une fonction parent hérite du scope de variables de cette fonction parent.Si une variable est déclarée dans le scope d'une fonction enfant et que cette variable a le même nom qu'une variable dans le scope de la fonction parent, alors la variable du scope de la fonction enfant masque la variable du scope de la fonction parent.
Exemple :
function parent_1() { var a = 1; var b = 1; var c = 1; console.log('\n parent :'); console.log('a : ',a); // a : 1 => scope de "parent_1" console.log('b : ',b); // b : 1 => scope de "parent_1" console.log('c : ',c); // c : 1 => scope de "parent_1" function child_1() { var b = 2; var c = 2; console.log('\n child_1 :'); console.log('a : ',a); // a : 1 => scope de "parent_1" console.log('b : ',b); // b : 2 => scope de "child_1" console.log('c : ',c); // c : 2 => scope de "child_1" function child_1_1() { var c = 3; console.log('\n child_1_1 :'); console.log('a : ',a); // a : 1 => scope de "parent_1" console.log('b : ',b); // b : 2 => scope de "child_1" console.log('c : ',c); // c : 3 => scope de "child_1_1" } child_1_1(); } child_1(); } parent_1();
Les paramètres d'une fonction sont dans le scope de cette fonction
Les paramètres d'une fonction sont dans le scope des variables de cette fonction.Nous avons ainsi le même scope et comportement que la variable soit un paramètre de la fonction ou une variable déclarée via le mot clé
var
dans cette fonction.En reprenant l'exemple précédent d'héritage de scope, nous avons le même comportement avec les paramètres des fonctions :
function parent_1(a, b, c) { console.log('\n parent :'); console.log('a : ',a); // a : 1 => scope de "parent_1" console.log('b : ',b); // b : 1 => scope de "parent_1" console.log('c : ',c); // c : 1 => scope de "parent_1" function child_1(b, c) { console.log('\n child_1 :'); console.log('a : ',a); // a : 1 => scope de "parent_1" console.log('b : ',b); // b : 2 => scope de "child_1" console.log('c : ',c); // c : 2 => scope de "child_1" function child_1_1(c) { console.log('\n child_1_1 :'); console.log('a : ',a); // a : 1 => scope de "parent_1" console.log('b : ',b); // b : 2 => scope de "child_1" console.log('c : ',c); // c : 3 => scope de "child_1_1" } child_1_1(3); } child_1(2, 2); } parent_1(1, 1, 1);
Closures
Le mode de fonctionnement des scopes de variables dans les fonctions permet la déclaration de closures :- une fonction enfant définie dans une fonction parent conservera toujours un lien vers les variables du scope de cette fonction parent
Le fait que la fonction enfant conserve les variables du scope de la fonction parent permet de définir et de réutiliser ces variables définies à un instant donné dans le passé. Ceci permet de se constituer un sorte de contexte de variables et de valeurs réutilisables dans le futur.
Ce fonctionnement permet par exemple l'écriture de fonctions
callback
utilisées par exemple en JQuery avec les listener
pour gérer les réponses aux événements utilisateur. En effet, la fonction callback
conserve les variables telles que lors de sa définition et donc les liens vers les éléments auxquelles elle est liée.Exemple 1
Dans l'exemple ci-dessous, la fonctionfunc_child
conserve un lien vers les variables définis dans le scope de la fonction parent func_parent
. Lorsque func_child
est appelée depuis du code situé à l'extérieur de la fonction parent func_parent
, les valeurs des variables à la déclaration de la fonction func_child
ont été conservées.function func_parent(a, b) { var c = 3; var d = 4; var func_child = function() { return a+b+c+d; } return func_child; } var func_child_1 = func_parent(1,2); var func_child_2 = func_parent(11,12); console.log('func_child_1',func_child_1()); // résultat : a+b+c+d => 1+2+3+4 => 10 console.log('func_child_2',func_child_2()); // résultat : a+b+c+d => 11+12+3+4 => 30
Exemple 2
Autre exemple : on utilise une variable du scope de la fonction parent comme compteur pour la fonction enfant qui sera incrémenté à chaque appel de la fonction enfant. On déclare deux fonctions enfantsfunc_child_1
et func_child_2
à partir de deux appels différents à la fonction parent afin d'avoir deux scopes de variables différents et donc deux compteurs différents.
function func_parent(msg) { var counter = 0; var func_child = function() { counter += 1; return msg + counter; } return func_child; } var func_child_1 = func_parent('func_child_1 : counter = '); var func_child_2 = func_parent('func_child_2 : counter = '); console.log(func_child_1()); // 'func_child_1 : counter = 1' console.log(func_child_1()); // 'func_child_1 : counter = 2' console.log(func_child_2()); // 'func_child_2 : counter = 1' console.log(func_child_1()); // 'func_child_1 : counter = 3' console.log(func_child_2()); // 'func_child_2 : counter = 2'