Variables (var, let, const) et scope

DÉCLARER UNE VARIABLE

Jusqu’à maintenant en js pour déclarer une variable nous utilisions le mot clé var. Les variables déclarées à l’aide de var ont leur scope définit par la function dans laquelle elles sont déclarées.
Il est possible de déclarer, mettre à jour une variable, redéclarer une nouvelle variable avec le nom d’une variable existante sans déclencher d’erreurs.

var yolo = 100;
console.log(yolo); // affiche : 100

yolo = 200
console.log(yolo); // affiche : 200

var yolo = 'Hello'; // affiche Hello

Les exemples ci dessous nous permettent de voir déjà un comportement de javascript: redéclarer une variable déjà définit sans déclencher d’alertes ou d’erreurs.

HOISTING

Voici un autre exemple « étrange » :

console.log(yolo); // affiche hello
var yolo = "hello";

Ce qui est étrange est qu’on accéde à la var yolo déclarée après le console.log. Au lieu d’avoir une référence error on aura yolo = undefined ( yolo existe déjà en js). La déclaration des variables est gérée en js grace au hoisting.

Pour comprendre le principe, éxécutons le code suivant


    console.log(foo);
 

foo n’étant pas déclarée nous obtenons : Uncaught ReferenceError: foo is not defined.


    var foo;
    console.log( foo );
    var bar = 1;
    console.log( bar );
 

foo est déclarée cette fois-ci, mais il ne lui a pas été assigné de valeur, elle est donc : undefined. bar est déclarée et une valeur lui a été assignée, elle renvoie donc 1.


    console.log( foo );
    console.log( bar );
    var bar = 1;
    var foo;
  

Cette fois foo et bar renvoient undefined alors qu’ils devraient renvoyer ReferenceError: foo is not defined. Le moteur javascript s’est comporté comme s’il avait réécrit le code comme cela.


    var foo, bar;
    console.log( foo );
    console.log( bar );
    bar = 1;

C’est le hoisting (remontée). Quand la compilation a lieu, le moteur semble remonter (hoist) toutes les déclarations de variables en haut du contexte d’exécution pour exécuter le code seulement ensuite.

LA PORTÉE EN JS (SCOPE)

La portée des variables utilisant var est définit comme suit:

  • « function scoped » si la variable est définit à l’intérieur d’une fonction, ce qui rend la variable accessible seulement à l’intérieur de la fonction dans laquelle elle a été déclarée.
  • « Globally scoped » si non définit dans une fonction, ce qui la rend accessible globalement.

Avec var la porté est défini par les accolades des functions seulement. Une var déclaré dans une condition aura une portée globale ou celle de la fonction dans laquelle est se trouve, contrairement à d’autres langages proches du C où on aurait une réference error

    function hello(){
        var yolo = "world";
    }
    console.log(yolo);// affiche: [Error] 'yolo' is not defined

    if(true) {
        var yolo2 = "hello"
    }
    console.log(yolo2);// affiche: hello
 

Ici on peut accéder à yolo2 alors que dans d’autres langages proches du C cela ne serait pas possible, le scope de yolo2 serait window, il serait accessible globalement ce qui est une mauvaise pratique car :

  • on peut avoir des problèmes de collision de nommage – plusieurs dev travaillant sur un même projet peuvent sans s’en rendre compte redéfinir une variable et donc l’écraser.
  • sécurité – la variable étant globale tout le monde y accès. De ce fait n’importe qui peut voir et modifier vos variables.

LET

Les variables définies avec let ont le même comportement que celles défines avec var à la différence que celles définies avec let voient leur portée définie par les accolades, leur bloc (« block scoped »).

De plus il n’est lus possible de rédéfinir une var créer précédement.


    let yolo = 100;
    console.log(yolo); // affiche : 100

    yolo = 200
    console.log(yolo); // affiche : 200

    let yolo = 'Hello'; // affiche: [Error] Identifier 'yolo' has already been declared
    console.log(yolo); // affiche : 200

On peut maintenant utiliser let pour nos boucles car la variable servant à l’itération ne sera accessible que dans le bloc où elle a été définie: plus de problème de i définie dans une boucle et entrant en conflit avec une autre boucle.

CONST

Const nous permet uniquement de declarer une variable, qui doit d’ailleurs être initialisé lors de la déclaration. Comme let les variables sont accessibles seulement dans les blocs (if, function etc.) et ne peuvent être redéfinies. Cependant contrairement à let qui nous permet de modifier une variable, la modification de la variable n’est pas possible… enfin presque pas possible

    const yolo = 100;
    console.log(yolo); // affiche : 100

    yolo = 200
    console.log(yolo); // affiche : [Error] Assignmen to constant variable
    console.log(yolo); // affiche : 100

Pourquoi je disais presque ? Const n’est pas entièrement immuable. Lorsque const est un objet, il n’est pas possible de rédéfinir la variable mais il est tout à fait possible de modifier les propriétés de l’objet.
Ce n’est pas une vraie constante au sens valeur de variable. C’est une constante au niveau référence: c’est à dire que le contenu d’un tableau ou d’un objet déclaré avec constbloque la réassignation de la variable, mais ne rend pas la valeur immuable.


    const yolo = {
        id:'1',
        name: 'Coco',
        city: 'paris'
    }

    yolo = {
        id:'1',
        name: 'Cococo',
        city: 'Nice'
    }

    // Affiche: [Error] Assignment to constant variable

    console.log(yolo);
    // Affiche:  Object { id:'1', name: 'Coco', city: 'paris' }

    yolo.name = "John Dodo";
    // Affiche:  Object { id:'1', name: 'John Dodo', city: 'paris' }

   // Il est aussi possible d'instancier un objet et de modifier par la suite le contenu de l'objet

    const oklm = {};
    oklm.name = "booba"; // fonctionne
    // mais
    const oklm = {};
    oklm = { name : "booba"}; // syntax error

    // c'est la même chose pour les tableaux
    const oklm = [];
    oklm.push({name : "booba"}]; // fonctionne
    // mais
    const oklm = []; // syntax error

Pour réellement rendre un objet immuable, il faut utiliser Object.freeze() qui empêche de rajouter de nouvelle propriété à un objet, d’en supprimer ou de modifier les propriétés existantes.

POUR CONCLURE

Les bonnes pratiques actuelles sont :

  • utiliser const pour toutes les déclarations de variables par défaut
  • utiliser let seulement si la variable doit être mise à jour
  • ne jamais utiliser var en es6

PS: Attention let et const ne bénéficie pas du mécanisme de hoisting, de ce fait si la variable est déclarée après son utilisation elle n’existera pas.