JavaScript : Qu'est ce que le hoisting ?
#1 En JavaScript on peut appeler une fonction avant de l'avoir déclarée
C'est assez surprenant, mais c'est la réalité. Evidemment, il y a une raison à cela. Vous avez déjà dû le constater, mais je vais faire un exemple, pour le vérifier.
Ci-dessous, j'appelle la fonction afficheResultats
, alors qu'elle n'est pas encore déclarée. Et pourtant tout se passe bien. Je déclare aussi une variable x
que j'initialise à 0
. Nous verrons plus loin pourquoi je fais ça.
#2 Expliquer ce fonctionnement par le hoisting
Pour expliquer ce fonctionnement, une explication existe : le hoisting (hissage en français). Le raisonnement consiste à dire, que toutes les déclarations, aussi bien de variables que de fonctions, sont hissées vers le haut de leur espace de portée.
Ainsi, la fonction pourrait être appelée avant d'être déclarée puisque déplacée vers le haut de la portée globale par le moteur JavaScript.
Il se trouve que c'est une explication qui est donnée, disons pour "faire simple", mais qu'en réalité aucune instruction n'est déplacée.
#3 Ce qui se passe en réalité
La vraie explication me parait beaucoup plus simple. En fait, le moteur JavaScript analyse deux fois le code :
- Dans la première analyse, il s'intéresse aux déclarations. Il les mémorise.
- Dans la deuxième analyse, il va procéder à l'exécution.
Il me semble que l'on comprend facilement que dans ces conditions, le moteur JavaScript puisse exécuter une fonction lors d'une deuxième analyse, alors qu'il l'a identifiée cette même fonction, dans une première analyse.
La conséquence, c'est que l'ordre d'exécution des instructions se trouve modifié. La déclaration de la fonction afficheResultat
(* ligne 12 à 14) va être prise en compte avant l'exécution de la fonction (* ligne 10).
Dans ces conditions, on comprend qu'on puisse interpréter cette situation comme un déplacement des instructions. Mais il faut s'empresser de donner l'explication, car si les instructions se déplacent dans le code, c'est pour le moins troublant.
Alors, c'est intéressant de regarder ce qui se passe au niveau de la déclaration de la variable let x=0
. Dans la première analyse, le moteur va s'intéresser au let x
et dans la deuxième analyse, il va s'intéresser au x=0
. Donc une instruction de ce type est exécutée en deux temps. Elle est en quelque sorte coupée en deux. Surprenant non !
Pour être précis, je dois ajouter que chaque déclaration est rangée dans son espace de portée. Les déclarations, lues dans l'espace global, seront "rangées en haut" de l'espace global. Celles lues, dans une fonction, seront "rangées en haut" dans la fonction. Celles lues, dans un bloc, seront "rangées en haut" du bloc.
#4 On fait un exemple
- Je déclare une variable globale
test
et je l'initialise à la chaîne'globale'
. - Je déclare une fonction
func
dans laquelle je fais unconsole.log(test)
. - Je déclare à nouveau une variable
test
que j'initialise à la chaîne'locale'
. - J'invoque la fonction.
Maintenant, je pose la question : que va afficher le concole.log
de test
?
On va raisonner avec le hoisting pour expliquer cette réponse :
- La deuxième déclaration de la variable
test
est coupée en deux. - La déclaration
var test
est hissée en haut de la fonction. Elle masque la première déclaration de la variabletest
initialisée à'globale'
. - L'initialisation de
test='locale'
ne bouge pas. - Le
console.log
affiche doncundefined
.
Tout se passe comme si on exécutait le code ci-dessous :
Il faut remarquer que j'ai utilisé exceptionnellement des var
pour mes déclarations de variables de manière à produire cette situation un petit peu tordue 🙃. Avec des let
, c'est une exception qui serait levée.
En conclusion : le meilleur moyen de ne pas avoir à se préoccuper du hoisting c'est d'écrire son code proprement et de déclarer ses variables en haut des différentes portées, d'utiliser des let
ou des const
, d'utiliser le mode strict.