Les pièges de JavaScript : cinq problèmes que vous rencontrerez encore et encore
Vous avez déployé du code qui a passé le linting, fonctionnait en développement, et qui s’est quand même cassé en production. Le bug semblait évident rétrospectivement—un await manquant, un tableau muté, un this qui pointait vers quelque chose d’inattendu. Ces pièges JavaScript persistent parce que la flexibilité du langage crée des trappes subtiles que les outils modernes ne détectent pas toujours.
Voici cinq erreurs JS courantes qui continuent d’apparaître dans les bases de code réelles, accompagnées de moyens pratiques pour les éviter.
Points clés à retenir
- Utilisez l’égalité stricte (
===) pour éviter les comportements inattendus de coercition de type - Les fonctions fléchées préservent le
thisde leur portée englobante, tandis que les fonctions régulières lientthisdynamiquement - Privilégiez
constet déclarez les variables en haut de leur portée pour éviter les erreurs de zone morte temporelle - Utilisez
Promise.allpour les opérations asynchrones parallèles etPromise.allSettledlorsque vous avez besoin de résultats partiels - Utilisez des méthodes de tableau non-mutantes comme
toSorted()etstructuredClone()pour la copie profonde
La coercition de type surprend toujours
L’opérateur d’égalité faible de JavaScript (==) effectue une coercition de type, produisant des résultats qui semblent illogiques jusqu’à ce que vous compreniez l’algorithme sous-jacent.
0 == '0' // true
0 == '' // true
'' == '0' // false
null == undefined // true
[] == false // true
La solution est simple : utilisez l’égalité stricte (===) partout. Mais la coercition apparaît aussi dans d’autres contextes. L’opérateur + concatène lorsque l’un des opérandes est une chaîne :
const quantity = '5'
const total = quantity + 3 // '53', pas 8
Les bonnes pratiques JavaScript modernes suggèrent une conversion explicite avec Number(), String(), ou les littéraux de gabarit lorsque l’intention compte. La coalescence nulle (??) aide également ici—elle ne se replie que sur null ou undefined, contrairement à || qui traite 0 et '' comme falsy.
Le problème de liaison du this
La valeur de this dépend de la façon dont une fonction est appelée, pas de l’endroit où elle est définie. Cela reste l’un des pièges JavaScript les plus persistants.
const user = {
name: 'Alice',
greet() {
console.log(this.name)
}
}
const greet = user.greet
greet() // undefined—'this' est maintenant l'objet global
Les fonctions fléchées capturent le this de leur portée englobante, ce qui résout certains problèmes mais en crée d’autres lorsque vous avez réellement besoin d’une liaison dynamique :
const user = {
name: 'Alice',
greet: () => {
console.log(this.name) // 'this' fait référence à la portée externe, pas à 'user'
}
}
Utilisez les fonctions fléchées pour les callbacks où vous souhaitez préserver le contexte. Utilisez les fonctions régulières pour les méthodes d’objet. Lors du passage de méthodes en tant que callbacks, liez explicitement ou enveloppez dans une fonction fléchée.
Le hoisting et la zone morte temporelle
Les variables déclarées avec let et const sont hissées (hoisted) mais non initialisées, créant une zone morte temporelle (TDZ) où y accéder lance une ReferenceError :
console.log(x) // ReferenceError
let x = 5
Cela diffère de var, qui hisse et initialise à undefined. La TDZ existe depuis le début du bloc jusqu’à ce que la déclaration soit évaluée.
Les déclarations de fonction sont complètement hissées, mais pas les expressions de fonction :
foo() // fonctionne
bar() // TypeError: bar is not a function
function foo() {}
const bar = function() {}
Déclarez les variables en haut de leur portée et privilégiez const par défaut. Cela élimine les surprises de la TDZ et signale clairement l’intention.
Discover how at OpenReplay.com.
Les pièges asynchrones en JavaScript
Oublier await est courant, mais des erreurs asynchrones plus subtiles causent plus de dégâts. Les awaits séquentiels lorsque l’exécution parallèle est possible font perdre du temps :
// Lent : s'exécute séquentiellement
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()
// Rapide : s'exécute en parallèle
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
])
Un autre problème fréquent : Promise.all échoue rapidement. Si une promesse est rejetée, vous perdez tous les résultats. Utilisez Promise.allSettled lorsque vous avez besoin de résultats partiels :
const results = await Promise.allSettled([fetchA(), fetchB(), fetchC()])
const successful = results.filter(r => r.status === 'fulfilled')
Gérez toujours les rejets. Les rejets de promesse non gérés peuvent faire planter les processus Node et causer des échecs silencieux dans les navigateurs.
Mutation vs immutabilité en JavaScript
Muter des tableaux et des objets crée des bugs difficiles à tracer, en particulier dans les frameworks avec état réactif :
const original = [3, 1, 2]
const sorted = original.sort() // Mute original !
console.log(original) // [1, 2, 3]
Le JavaScript moderne fournit des alternatives non-mutantes. Utilisez toSorted(), toReversed(), et with() pour les tableaux. Pour les objets, la syntaxe de décomposition crée des copies superficielles :
const sorted = original.toSorted()
const updated = { ...user, name: 'Bob' }
N’oubliez pas que la décomposition crée des copies superficielles. Les objets imbriqués partagent toujours des références :
const copy = { ...original }
copy.nested.value = 'changed' // Change aussi original.nested.value
Pour le clonage profond, utilisez structuredClone() ou gérez les structures imbriquées explicitement.
Conclusion
Ces cinq problèmes—coercition de type, liaison du this, hoisting, mauvaise utilisation de l’asynchrone et mutation accidentelle—représentent une part disproportionnée des bugs JavaScript. Les reconnaître en revue de code devient automatique avec la pratique.
Activez des règles ESLint strictes comme eqeqeq et no-floating-promises. Envisagez TypeScript pour les projets où la sûreté de type compte. Plus important encore, écrivez du code qui rend l’intention explicite plutôt que de vous fier aux comportements implicites de JavaScript.
FAQ
JavaScript inclut les deux pour des raisons historiques et de flexibilité. L'opérateur d'égalité faible (==) effectue une coercition de type avant la comparaison, ce qui peut être utile mais produit souvent des résultats inattendus. L'opérateur d'égalité stricte (===) compare à la fois la valeur et le type sans coercition. La bonne pratique moderne favorise fortement === car il rend les comparaisons prévisibles et réduit les bugs.
Utilisez les fonctions fléchées pour les callbacks, les méthodes de tableau et les situations où vous souhaitez préserver le contexte this environnant. Utilisez les fonctions régulières pour les méthodes d'objet, les constructeurs et les cas où vous avez besoin d'une liaison this dynamique. La différence clé est que les fonctions fléchées lient lexicalement this depuis leur portée englobante, tandis que les fonctions régulières déterminent this en fonction de la façon dont elles sont appelées.
La zone morte temporelle (TDZ) est la période entre l'entrée dans une portée et le point où une variable let ou const est déclarée. Accéder à la variable pendant cette période lance une ReferenceError. Évitez les problèmes de TDZ en déclarant les variables en haut de leur portée et en privilégiant const par défaut. Cela rend votre code plus prévisible et plus facile à lire.
Utilisez Promise.all lorsque toutes les promesses doivent réussir pour que votre opération ait un sens—elle échoue rapidement si une promesse est rejetée. Utilisez Promise.allSettled lorsque vous avez besoin de résultats de toutes les promesses indépendamment des échecs individuels, comme lors de la récupération de données depuis plusieurs sources optionnelles. Promise.allSettled renvoie un tableau d'objets décrivant chaque résultat comme fulfilled ou rejected.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.