Le hoisting JavaScript est l'un des concepts les plus importants a comprendre, surtout si vous travaillez avec ce langage. Il y a une difference avec d'autres langages de programmation. En JavaScript, les declarations de variables et de fonctions sont "hoistees" en haut de leur scope contenant avant l'execution du code. Ce comportement peut mener a des resultats etranges/inattendus s'il est mal compris.
Dans ce guide, nous allons couvrir tout ce que vous devez savoir sur le hoisting JavaScript. Nous allons commencer
par expliquer comment les declarations var et function sont hoistees.
Ensuite, nous verrons comment let et const se comportent.
Enfin, nous verrons comment ecrire un meilleur code JavaScript et eviter les problemes courants.
Qu'est-ce que le hoisting en JavaScript ?
Le hoisting est une specificite JavaScript ou les declarations de variables et de fonctions sont deplacees en haut de leur scope contenant avant l'execution du code. Cela signifie que vous pouvez utiliser des variables et des fonctions avant qu'elles ne soient declarees dans votre code.
Cependant, il est important de comprendre que seules les declarations sont hoistees, pas les
initialisations. Pour les variables declarees avec var, elles sont hoistees et
initialisees a undefined. Pour les
fonctions, toute la declaration de fonction est hoistee, ce qui la rend disponible dans tout le scope.
Exemple basique de hoisting
// This code works because of hoisting
console.log(x); // undefined (not an error!)
var x = 5;
// This is how JavaScript interprets the code above:
var x; // Declaration is hoisted
console.log(x); // undefined
x = 5; // Initialization stays in place
Comment fonctionne le hoisting des fonctions
Les declarations de fonctions sont entierement hoistees en JavaScript. Cela signifie que la declaration et le corps de la fonction sont deplaces en haut du scope. Cela signifie que vous pouvez appeler des fonctions avant qu'elles ne soient definies dans votre code.
Hoisting des declarations de fonctions
// This works because function declarations are hoisted
greet(); // "Hello, World!"
function greet() {
console.log("Hello, World!");
}
// This is how JavaScript interprets it:
function greet() {
console.log("Hello, World!");
}
greet(); // "Hello, World!"
Function expression vs function declaration
Il est crucial de comprendre que les function expressions ne sont PAS hoistees. Seules les function declarations sont hoistees. C'est une distinction importante :
// Function declaration - HOISTED
sayHello(); // "Hello!"
function sayHello() {
console.log("Hello!");
}
// Function expression - NOT HOISTED
sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function() {
console.log("Goodbye!");
};
// How JavaScript interprets the function expression:
var sayGoodbye; // Declaration hoisted, initialized with undefined
sayGoodbye(); // TypeError: sayGoodbye is undefined
sayGoodbye = function() {
console.log("Goodbye!");
};
Pourquoi le hoisting des fonctions est utile
Le hoisting des fonctions vous permet d'organiser votre code en placant l'algorithme principal en haut et les fonctions helper en dessous, ce qui peut ameliorer la lisibilite :
function processItems(items) {
// Main algorithm at the top
return items
.filter(itemMatches)
.map(transformItem);
// Helper functions below
function itemMatches(item) {
return item.status === 'active';
}
function transformItem(item) {
return {
id: item.id,
name: item.name.toUpperCase()
};
}
}
// This works because both functions are hoisted
const result = processItems([
{ id: 1, name: 'Alice', status: 'active' },
{ id: 2, name: 'Bob', status: 'inactive' }
]);
console.log(result); // [{ id: 1, name: 'ALICE' }]
Hoisting des variables avec var
Les variables declarees avec var sont hoistees en haut de
leur scope de fonction (ou du scope global si elles sont declarees hors d'une fonction). Cependant, seule la
declaration est hoistee, pas l'initialisation. La variable est initialisee a undefined tant que la ligne
d'assignation n'est pas executee.
Hoisting var basique
function example() {
console.log(x); // undefined (not an error!)
var x = 10;
console.log(x); // 10
}
// How JavaScript interprets this:
function example() {
var x; // Declaration hoisted
console.log(x); // undefined
x = 10; // Initialization stays in place
console.log(x); // 10
}
Le probleme avec le hoisting de var
Le comportement de hoisting de var peut mener a des bugs
inattendus, surtout dans des boucles avec des operations asynchrones :
const people = ['Alice', 'Bob', 'Claire', 'David'];
for (var i = 0; i < people.length; i++) {
setTimeout(function() {
console.log(people[i]); // Outputs: undefined (4 times)
}, 100);
}
// Why this happens:
// The variable 'i' is hoisted to the function scope
// By the time the setTimeout callbacks execute, the loop has finished
// and 'i' equals people.length (4), which is out of bounds
// How JavaScript interprets this:
var i; // Hoisted to function scope
for (i = 0; i < people.length; i++) {
setTimeout(function() {
console.log(people[i]); // All callbacks see the same 'i' value
}, 100);
}
// After loop completes, i = 4, so all callbacks log undefined
La solution historique : IIFE
Avant ES2015, les developpeurs utilisaient des Immediately Invoked Function Expressions (IIFE) pour creer un nouveau scope pour chaque iteration :
const people = ['Alice', 'Bob', 'Claire', 'David'];
for (var i = 0; i < people.length; i++) {
(function(index) {
setTimeout(function() {
console.log(people[index]); // Works correctly
}, 100);
})(i);
}
// Output: Alice, Bob, Claire, David (in order)
Scope de bloc et variables ES2015+
ES2015 a introduit let
et const, qui ont un
comportement de hoisting fondamentalement different par rapport a var. Ces nouveaux mots-cles
fournissent un scope au niveau du bloc et ne sont pas hoistes de la meme maniere.
Scope de bloc avec let et const
Les variables declarees avec let et const sont scopees au bloc
dans lequel elles sont declarees, pas a toute la fonction :
function demonstrateScope() {
if (true) {
var functionScoped = 'I am function scoped';
let blockScoped = 'I am block scoped';
const alsoBlockScoped = 'I am also block scoped';
}
console.log(functionScoped); // "I am function scoped" (works!)
console.log(blockScoped); // ReferenceError: blockScoped is not defined
console.log(alsoBlockScoped); // ReferenceError: alsoBlockScoped is not defined
}
Resoudre le probleme de boucle avec let
Le probleme de boucle avec var se resout facilement en
utilisant let, qui cree
un nouveau binding a chaque iteration :
const people = ['Alice', 'Bob', 'Claire', 'David'];
for (let i = 0; i < people.length; i++) {
setTimeout(function() {
console.log(people[i]); // Works correctly!
}, 100);
}
// Output: Alice, Bob, Claire, David (in order)
// Why this works:
// Each iteration creates a new 'i' binding in its own block scope
// Each setTimeout callback captures its own 'i' value
La Temporal Dead Zone (TDZ)
La Temporal Dead Zone (TDZ) est la periode entre l'entree dans un scope et la declaration effective
d'une variable. Pendant ce temps, la variable ne peut pas etre accessible. Cela s'applique aux declarations let, const et class.
Comprendre la Temporal Dead Zone
// Temporal Dead Zone starts here
console.log(myVar); // ReferenceError: Cannot access 'myVar' before initialization
let myVar = 10;
// Temporal Dead Zone ends here
// This is different from var:
console.log(myVar2); // undefined (not an error)
var myVar2 = 10;
Pourquoi la Temporal Dead Zone existe
La TDZ aide a detecter tot les erreurs de programmation en empechant l'acces aux variables avant leur initialisation. Cela rend le code plus previsible et plus facile a debugger :
function example() {
// TDZ starts
console.log(typeof myLet); // ReferenceError (not "undefined")
let myLet = 'initialized';
// TDZ ends
}
// Compare with var:
function example2() {
console.log(typeof myVar); // "undefined" (no error)
var myVar = 'initialized';
}
Ordre et priorite du hoisting
Quand plusieurs declarations existent dans le meme scope, JavaScript les hoiste dans un ordre specifique :
- Les declarations de fonctions sont hoistees en premier
- Les declarations de variables (
var) sont hoistees en second - Les function expressions ne sont pas entierement hoistees (seule la declaration de variable)
Exemple d'ordre de hoisting
// What you write:
console.log(typeof myFunc); // "function"
console.log(typeof myVar); // "undefined"
function myFunc() {
return 'I am a function';
}
var myVar = 'I am a variable';
// How JavaScript interprets it:
function myFunc() { // Function hoisted first
return 'I am a function';
}
var myVar; // Variable declaration hoisted second
console.log(typeof myFunc); // "function"
console.log(typeof myVar); // "undefined"
myVar = 'I am a variable';
Conflits de nom entre fonction et variable
Quand une fonction et une variable partagent le meme nom, la declaration de fonction est prioritaire :
console.log(typeof myName); // "function" (not "undefined")
function myName() {
return 'I am a function';
}
var myName = 'I am a variable';
console.log(typeof myName); // "string" (after assignment)
// How JavaScript interprets this:
function myName() { // Function hoisted first
return 'I am a function';
}
var myName; // Variable declaration (ignored, name already exists)
console.log(typeof myName); // "function"
myName = 'I am a variable'; // Assignment overwrites the function
console.log(typeof myName); // "string"
Bonnes pratiques et JavaScript moderne
Comprendre le hoisting est essentiel, mais les bonnes pratiques de JavaScript moderne recommandent d'eviter les pieges lies au hoisting :
1. Eviter var - utiliser let et const a la place
Depuis ES2015, var
devrait etre evite au profit de let et const :
// ❌ Avoid var
function badExample() {
if (true) {
var x = 10;
}
console.log(x); // 10 (unexpected function scope)
}
// ✅ Use let or const
function goodExample() {
if (true) {
let x = 10;
}
console.log(x); // ReferenceError (expected block scope)
}
2. Utiliser const par defaut
L'approche moderne JavaScript est d'utiliser const par defaut et
n'utiliser let que si
vous devez reassigner la variable :
// ✅ Use const by default
const userName = 'Alice';
const userAge = 25;
const userRoles = ['admin', 'user'];
// Only use let when reassignment is needed
let counter = 0;
counter = 1; // Valid reassignment
// const prevents accidental reassignment
const maxItems = 100;
maxItems = 200; // TypeError: Assignment to constant variable
3. Declarer les variables en haut de leur scope
Meme si le hoisting rend possible l'utilisation de variables avant leur declaration, il est preferable de les declarer en haut de leur scope pour plus de clarte :
// ❌ Confusing due to hoisting
function confusing() {
console.log(value);
var value = 10;
}
// ✅ Clear and explicit
function clear() {
const value = 10;
console.log(value);
}
4. Utiliser les function declarations pour beneficier du hoisting
Les function declarations peuvent etre utiles quand vous voulez organiser le code avec l'algorithme principal en haut :
// ✅ Good use of function hoisting
function processData(data) {
// Main algorithm at top
const filtered = data.filter(isValid);
const transformed = filtered.map(transform);
return transformed;
// Helper functions below
function isValid(item) {
return item.status === 'active';
}
function transform(item) {
return { id: item.id, name: item.name.toUpperCase() };
}
}
Pieges courants du hoisting et solutions
Piege 1 : acceder aux variables avant leur declaration
// ❌ Problem with var
function problem() {
console.log(x); // undefined (confusing!)
var x = 5;
}
// ✅ Solution: Use let/const
function solution() {
// console.log(x); // ReferenceError (clear error message)
const x = 5;
console.log(x); // 5
}
Piege 2 : variables de boucle dans des callbacks async
// ❌ Problem with var
const items = ['a', 'b', 'c'];
for (var i = 0; i < items.length; i++) {
setTimeout(() => console.log(items[i]), 100);
}
// Output: undefined, undefined, undefined
// ✅ Solution: Use let
const items = ['a', 'b', 'c'];
for (let i = 0; i < items.length; i++) {
setTimeout(() => console.log(items[i]), 100);
}
// Output: a, b, c
Piege 3 : function expression vs declaration
// ❌ Function expression not hoisted
myFunction(); // TypeError: myFunction is not a function
var myFunction = function() {
console.log('Hello');
};
// ✅ Function declaration is hoisted
myFunction(); // "Hello"
function myFunction() {
console.log('Hello');
}
Hoisting des classes
ES2015 a introduit les declarations class, qui se comportent
comme let et const :
// ❌ Class is in TDZ before declaration
const instance = new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {
constructor() {
this.value = 10;
}
}
// ✅ Use class after declaration
class MyClass {
constructor() {
this.value = 10;
}
}
const instance = new MyClass(); // Works correctly
Exemples concrets
Exemple 1 : organiser le code avec le hoisting des fonctions
// Main algorithm at the top, helpers below
function validateUserData(userData) {
// Main validation logic
if (!isValidEmail(userData.email)) {
return { valid: false, error: 'Invalid email' };
}
if (!isValidAge(userData.age)) {
return { valid: false, error: 'Invalid age' };
}
if (!isValidName(userData.name)) {
return { valid: false, error: 'Invalid name' };
}
return { valid: true };
// Helper functions (hoisted, so they work above)
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function isValidAge(age) {
return typeof age === 'number' && age >= 0 && age <= 120;
}
function isValidName(name) {
return typeof name === 'string' && name.trim().length >= 2;
}
}
// Usage
const result = validateUserData({
email: '[email protected]',
age: 25,
name: 'Alice'
});
console.log(result); // { valid: true }
Exemple 2 : eviter les pieges courants des boucles
// Modern approach with let
function attachEventListeners(buttons) {
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log(`Button ${i} clicked`); // Each button logs its correct index
});
}
}
// Even better: Use forEach
function attachEventListenersModern(buttons) {
buttons.forEach((button, index) => {
button.addEventListener('click', function() {
console.log(`Button ${index} clicked`);
});
});
}
Resume : points cles
- Les function declarations sont entierement hoistees et peuvent etre appelees avant d'apparaitre dans le code
- Les declarations var sont hoistees mais initialisees a
undefined - let et const sont dans la Temporal Dead Zone jusqu'a leur ligne de declaration
- Les function expressions ne sont pas hoistees (seule la declaration de variable l'est)
- Utilisez const par defaut,
letquand une reassignment est necessaire, et evitez var - Le hoisting des fonctions peut etre utile pour organiser le code avec la logique principale en haut
- Le scope de bloc avec
letetconstevite de nombreux bugs courants
Conclusion
Le hoisting JavaScript est un concept fondamental que chaque developpeur JavaScript devrait comprendre. Meme s'il
peut mener a des comportements inattendus avec var, les bonnes pratiques
JavaScript modernes avec let et const aident a eviter ces
problemes.
Le hoisting des fonctions peut etre utile pour organiser le code, en vous permettant de placer l'algorithme principal en haut d'une fonction et les fonctions helpers en dessous. Cependant, il est important de comprendre les differences entre function declarations et function expressions.
En comprenant le comportement du hoisting, la Temporal Dead Zone et en suivant les bonnes pratiques JavaScript
modernes, vous pouvez ecrire du code plus previsible et plus maintenable. Pensez a utiliser const par defaut, let quand c'est necessaire,
et evitez var dans du
code JavaScript moderne.