Hoisting en JavaScript

first

Nous allons aujourd'hui nous intéresser aux principes de base du hoisting (hissage en anglais). Comment la déclaration des variables et fonctions est gérée en JavaScript et pourquoi on peut rencontrer ce qui semblera être un comportement aberrrant du language aux autres développeurs.

Qu'est ce que le hoisting

Au cas où tu ne serais pas familier avec ce principe, voici un petit morceau de code pour le voir en action. Hésite pas à le taper dans ta console pour t'aider à mieux saisir le concept.

'use strict'

var a = 5,
  b = 10;

if (b > a){
  c = a + b + c
  let c = 2
  console.log(c)
};

console.log(c);

//   c = a + b + c;
//               ^ 
// ReferenceError: Cannot access 'c' before initialization

On va rencontrer ici une erreur, nous indiquant que la variable c (dans le calcul) ne peut être utilisée avant son initialisation. En effet elle est initialisée la ligne en dessous. Voilà un premier cas concret d'utilisation du concept de hissage.

Si je souhaite utiliser ma variable c avant sa déclaration il va falloir que je déclare ma variable avec le mot clé var, en effet seules les variables déclarées avec le mot clé var peuvent être hissées. Ne fonctionne pas avec les mots clés let et const

Donc si je souhaite que mon code fonctionne en utilisant le concept d'hoisting je dois juste effectuer cette petite modification :

'use strict'

var a = 5,
  b = 10;

if (b > a){
  c = a + b + c
  var c = 2
  console.log(c)
};

console.log(c);

// 2 
// 2

Avec le mot clé var, quand la compilation a lieu, le moteur semble hisser toutes les déclarations de variables en haut du code pour exécuter le code seulement ensuite. La portée d'une variable déclarée avec le mot clé var est le contexte d'éxécution courant c'est à dire : la fonction qui contient la déclaration (ce qui est notre cas dans cet exemple) ou le contexte global si la variable est déclarée en dehors de toute fonction.

Tu l'auras compris, SI tu déclares une variable avec le mot clé let ou const il faut toujours qu'elle soit déclarée avant son 1er usage.

Cela devrait t'aider à y voir plus clair concernant le hissage de variable en JavaScript, j'en profite pour apporter une petite précision. La définition stricte du hissage comme on l'imagine plus haut, c'est à dire remonter en haut du code les déclarations de variables, n'est pas ce qui se passe précisèment.

En réalité les déclarations de variables ainsi que les déclarations de fonctions, sont mises en mémoire pendant la phase de compilation MAIS restent exactement à leur place dans le code.

Étrange ? Non c'est la spec.

JavaScript est une implémentation de la spécification ECMAScript, et le début de la section 10.5 : Déclaration Binding Instantiation est claire :


Every execution context has an associated VariableEnvironment. Variables and functions declared in ECMAScript code evaluated in an execution context are added as bindings in that VariableEnvironment’s Environment Record. For function code, parameters are also added as bindings to that Environment Record.


Which Environment Record is used to bind a declaration and its kind depends upon the type of ECMAScript code executed by the execution context, but the remainder of the behaviour is generic. On entering an execution context, bindings are created in the VariableEnvironment as follows using the caller provided code and, if it is function code, argument List args

Donc, quand le moteur JS entre dans un contexte d’exécution (une fonction par exemple), il commence par créer les bindings ; puis il exécute les instructions et assigne les valeurs. Ce qui signifie un certain nombre de choses pour les variables qu’il va rencontrer dans ce contexte :

Si le moteur rencontre une déclaration de variable (var = ...), il créé un binding dans le contexte courant.

Si le moteur ne peut trouver de déclaration de variable pour une variable utilisée dans un contexte, il va aller chercher récursivement dans les contextes parents jusqu’à trouver la déclaration en question et créer un binding dans le contexte courant qui sera une référence vers le binding du contexte contenant la déclaration.

Pour cette première étape de l’exécution d’un code JavaScript, le moteur peut adopter diverses stratégies. Il pourrait scanner le code pour chercher des déclarations, créer les bindings correspondant ; puis le re-scanner avec en mémoire ce set de bindings puis aller chercher les références manquantes dans les contextes parents. Il pourrait aussi créer des bindings à la volée vers les contextes parents ; et les détruire s’il rencontre une déclaration dans le contexte courant.

Une partie des moteurs JavaScript n’étant pas open source, c’est difficile de dire quelle est la stratégie employée. Dans tous les cas, la bonne pratique, est de déclarer vos variables au début de votre contexte d’exécution ; même sans leur assigner de valeur tout de suite.

Le contexte d’exécution courant garde une liste des bindings, c’est la table VariableEnvironment.

Techniquement, ce que nous appelons généralement le scope est en fait un LexicalEnvironment. Qui est un des trois composants du contexte d’exécution. C’est le fonctionnement théorique que vous trouverez décrit dans la spec. C’est l’endroit où les variables vont exister, mais c’est souvent confondu avec la liste des bindings ou carrément tout le contexte d’exécution. C’est une confusion qui ne prête généralement pas à conséquence ; mais c’est toujours mieux d’être au clair sur les concept que l’on manipule.

Le compilateur JavaScript

Petit retour sur la mention de compilateur. Oui, le JS est un language de script (et un language de programmation dynamique). Oui vous envoyez directement un script aux postes clients et pas un binaire compilé ou un exécutable. Mais tout de même ; pensez-vous que var foo = 1; ait un sens quelconque pour le processeur ? Ce n’est pas du tout le cas.

Pour ce qui suit, je parlerai de la façon dont ça fonctionne pour Chromium (et Chrome) et pour son moteur JS : V8. Parce-que les deux sont open-source, on peut facilement lire et vérifier le code. Les autres navigateurs et moteurs ne fonctionnent pas exactement de la même façon, mais produisent plus ou moins le même résultat ; modulo la vitesse..

Si vous explorez un peu le brillant code source de V8, vous pouvez voir qu’il en existe une version pour chaque plateforme majeure (ARM, x64, ia32…). Parce-que le moteur a besoin d’un language et d’un jeu d’instruction spécifique pour s’adresser à chaque famille de processeur. L’instruction “allouer 128 bits” de mémoire pour ma nouvelle variable ne sera pas écrite de la même façon pour votre macbook et pour votre nexus.

Donc oui, JavaScript est compilé, c’est juste que cela n’arrive que quelques instants avant son exécution sur la machine client.

Le hissage de fonction

Voici un autre exemple de hoisting avec une fonction

monChat('Edgar')

function monChat(nom){
  console.log(`Mon chat s'appel ${chat}`)
}

// Mon chat s'appel Edgar

Tu peux voir ici, que même si la fonction monChat() est appelée avant son initialisation, le code sera bien éxècuté et que nom vaudra Edgar. Ceci est possible grâce à la façon dont l'éxécution de contexte fonctionne en JavaScript.

Donc avec le concept de hoisting, on peut techniquement utiliser une fonction avant qu'elle existe => ne fonctionne pas avec les functions expression et arrow functions uniquement utilisable avec les function declaration. Voici un lien vers mon article sur les fonctions si tu souhaites en savoir plus sur les différentes fonctions: Les Fonctions en JavaScript

Personnellement je n'utilise pas trop le hoisting, je préfère déclarer mes fonctions et variables avant de les utiliser, mais c'est un concept intéressant à connaitre.


En conclusion, le hoisting n'est pas une étrangeté, c'est la façon dont le langage est sensé fonctionner. Mais si tu respectes quelques bonnes pratique de base tu ne devrais pas souvent rencontrer de problème de ce type. La seule règle à suivre est de déclarer tes variables et fonctions avant de les utiliser.

Latest Blogposts

ARRAY JavaScript

On aborde aujourd'hui le thème des tableaux qui sont eux aussi un élément incontournable de la programmation informatique.

23 January 2020