Tu viens de commencer JavaScript et ton code ne fonctionne jamais du premier coup ? Rassure-toi : tu n'es pas seul. Ces erreurs javascript debutant, absolument tous les developpeurs les ont faites -- meme les seniors qui aujourd'hui reviewent ton code avec un air superieur. La difference entre un debutant et un expert, ce n'est pas l'absence d'erreurs : c'est la capacite a les reconnaitre instantanement.Dans cet article, on passe en revue les 10 bugs javascript courants les plus frequents chez les debutants, avec a chaque fois le code qui plante, le code qui marche, et surtout pourquoi ca plante. On terminera avec 3 pieges javascript de niveau intermediaire et des regles d'or pour ecrire du JavaScript propre des le depart.Pret a transformer tes erreurs en lecons ? C'est parti.---## 1. == vs === -- L'egalite qui mentC'est probablement l'erreur courante js la plus classique. JavaScript propose deux operateurs d'egalite, et l'un d'eux est un menteur professionnel.❌ Le code qui trompe :``javascriptconsole.log(0 == ""); // trueconsole.log(0 == false); // trueconsole.log("" == false); // trueconsole.log(null == undefined); // trueconsole.log("1" == 1); // true`✅ Le code fiable :`javascriptconsole.log(0 === ""); // falseconsole.log(0 === false); // falseconsole.log("" === false); // falseconsole.log(null === undefined); // falseconsole.log("1" === 1); // false`L'operateur == effectue une conversion de type implicite (coercion) avant de comparer. Cela signifie que JavaScript transforme silencieusement les valeurs pour essayer de les rendre comparables, ce qui produit des resultats surprenants et des bugs difficiles a traquer. L'operateur === compare la valeur ET le type, sans aucune conversion. C'est la bonne pratique javascript a adopter systematiquement.Regle simple : utilise toujours === et !==. Oublie que == existe.---## 2. var vs let/const -- Le fantome du hoistingSi tu lis du code JavaScript ancien, tu verras var partout. Aujourd'hui, var est considere comme un piege a eviter. Pour bien maitriser les differences entre var, let et const, il faut comprendre le concept de hoisting.❌ Le code piege :`javascriptconsole.log(nom); // undefined (pas d'erreur !)var nom = "Alice";if (true) { var secret = "mot de passe";}console.log(secret); // "mot de passe" (fuite de variable !)`✅ Le code moderne :`javascriptconsole.log(nom); // ReferenceError: Cannot access 'nom' before initializationlet nom = "Alice";if (true) { let secret = "mot de passe";}console.log(secret); // ReferenceError: secret is not defined`var est "hoiste" en haut de la fonction (pas du bloc) et initialise a undefined. Cela signifie que tu peux utiliser une variable avant de la declarer sans obtenir d'erreur -- juste un undefined silencieux qui contaminera tout ton programme. let et const sont limites au bloc {} ou ils sont declares et provoquent une erreur explicite si tu les utilises trop tot.Regle simple : utilise const par defaut, let quand tu dois reassigner, var jamais.---## 3. this qui change de contexte -- Le cameleonLe mot-cle this en JavaScript est l'un des concepts les plus deroutants pour les debutants. Sa valeur change selon comment la fonction est appelee, pas ou elle est ecrite.❌ Le code qui perd le contexte :`javascriptconst utilisateur = { nom: "Alice", saluer: function() { setTimeout(function() { console.log("Bonjour, " + this.nom); }, 1000); }};utilisateur.saluer(); // "Bonjour, undefined"`✅ Le code avec arrow function :`javascriptconst utilisateur = { nom: "Alice", saluer: function() { setTimeout(() => { console.log("Bonjour, " + this.nom); }, 1000); }};utilisateur.saluer(); // "Bonjour, Alice"`Une fonction classique (function) cree son propre this, qui dans un setTimeout pointe vers window (ou undefined en mode strict). Une arrow function (=>) n'a pas de this propre : elle herite du this du contexte englobant. C'est pour cela que les arrow functions sont ideales pour les callbacks.---## 4. Muter un tableau au lieu d'en creer un nouveauC'est un piege javascript particulierement vicieux quand tu travailles avec React ou tout framework reactif. Modifier un tableau existant au lieu d'en creer un nouveau provoque des bugs invisibles.❌ Le code qui mute :`javascriptconst fruits = ["pomme", "banane"];const nouveaux = fruits;nouveaux.push("cerise");console.log(fruits); // ["pomme", "banane", "cerise"] -- modifie aussi !console.log(fruits === nouveaux); // true (meme reference)`✅ Le code immutable :`javascriptconst fruits = ["pomme", "banane"];const nouveaux = [...fruits, "cerise"];console.log(fruits); // ["pomme", "banane"] -- intactconsole.log(nouveaux); // ["pomme", "banane", "cerise"]console.log(fruits === nouveaux); // false (nouvelle reference)`En JavaScript, les tableaux et objets sont passes par reference, pas par valeur. Quand tu ecris const nouveaux = fruits, tu ne copies pas le tableau : tu crees un deuxieme nom qui pointe vers le meme tableau en memoire. Toute modification via l'un affecte l'autre. Le spread operator (...) cree une vraie copie (superficielle).---## 5. Oublier le return dans .map() / .filter()Tu utilises .map() pour transformer un tableau et tu obtiens un tableau de undefined ? Ce piege a fait perdre des heures a des milliers de debutants.❌ Le code sans return :`javascriptconst nombres = [1, 2, 3, 4, 5];const doubles = nombres.map(n => { n 2; // pas de return !});console.log(doubles); // [undefined, undefined, undefined, undefined, undefined]`✅ Le code correct :`javascriptconst nombres = [1, 2, 3, 4, 5];// Avec accolades : return explicite obligatoireconst doubles = nombres.map(n => { return n 2;});// Sans accolades : return impliciteconst doublesV2 = nombres.map(n => n * 2);console.log(doubles); // [2, 4, 6, 8, 10]console.log(doublesV2); // [2, 4, 6, 8, 10]`Quand tu utilises des accolades {} avec une arrow function, le return explicite est obligatoire. Sans accolades, la valeur est retournee implicitement. C'est une distinction subtile mais critique. Meme erreur avec .filter() : sans return, le predicat retourne undefined, qui est falsy, et tu obtiens un tableau vide.---## 6. Confondre synchrone et asynchroneC'est l'erreur qui rend fou quand on debute en JavaScript. Tu appelles une fonction, tu utilises le resultat juste apres... et il n'est pas encore la.❌ Le code qui va trop vite :`javascriptlet donnees;fetch("https://api.exemple.com/users") .then(response => response.json()) .then(data => { donnees = data; });console.log(donnees); // undefined -- le fetch n'est pas termine !`✅ Le code qui attend correctement :`javascriptasync function chargerDonnees() { const response = await fetch("https://api.exemple.com/users"); const donnees = await response.json(); console.log(donnees); // les donnees sont bien la return donnees;}chargerDonnees();`JavaScript est mono-thread et non bloquant. Les operations comme fetch, setTimeout, ou la lecture de fichiers sont asynchrones : elles sont lancees puis JavaScript continue d'executer le code suivant sans attendre. Le resultat arrive plus tard, dans un callback ou une Promise. Avec async/await, tu peux ecrire du code asynchrone qui se lit comme du code synchrone. Pour aller plus loin, consulte notre guide pour comprendre les Promises et async/await.---## 7. Comparer des objets et des tableaux par referenceVoici un des bugs javascript courants les plus difficiles a reperer quand on debute. Deux objets identiques en apparence ne sont jamais egaux.❌ Le code trompeur :`javascriptconsole.log([1, 2, 3] === [1, 2, 3]); // falseconsole.log({a: 1} === {a: 1}); // falseconst liste1 = [1, 2, 3];const liste2 = [1, 2, 3];console.log(liste1 === liste2); // false`✅ Le code qui compare correctement :`javascript// Pour les tableaux simplesconst liste1 = [1, 2, 3];const liste2 = [1, 2, 3];console.log(JSON.stringify(liste1) === JSON.stringify(liste2)); // true// Pour les objets simplesconst obj1 = { a: 1, b: 2 };const obj2 = { a: 1, b: 2 };console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true// Pour une comparaison element par elementfunction tableauxEgaux(a, b) { return a.length === b.length && a.every((val, i) => val === b[i]);}console.log(tableauxEgaux(liste1, liste2)); // true`En JavaScript, === compare la reference memoire pour les objets et tableaux, pas leur contenu. Deux objets avec exactement les memes proprietes sont quand meme differents car ils occupent deux emplacements memoire distincts. JSON.stringify est une solution rapide pour les cas simples, mais attention : l'ordre des cles compte, et les valeurs undefined ou les fonctions sont ignorees.---## 8. typeof null === 'object' et autres pieges de typeof`typeof semble simple et fiable. Il ne l'est pas. C'est l'un des plus vieux pieges javascript du langage.❌ Le code qui ment :``javascriptconsole.log(typeof null); // "object" -- c'est un bug historique !console.log(typeof [1, 2, 3]); // "object" -- pas "array" !console.log(typeof NaN); // "number" -- Not a Number est un number ?!console.log(typeof function(){}); // "function" -- OK, celui-la est correct`✅ Le code qui verifie correctement :`javascript// Verifier nullconst valeur = null;console.log(valeur === null); // true// Verifier si c'est un tableauconsole.log(Array.isArray([1, 2, 3])); // trueconsole.log(Array.isArray("texte")); // false// Verifier NaNconsole.log(Number.isNaN(NaN)); // trueconsole.log(Number.isNaN("texte")); // false (contrairement a isNaN() global)`typeof null === 'object' est un bug reconnu depuis la premiere version de JavaScript en 1995, mais il ne sera jamais corrige car cela casserait trop de code existant. Pour verifier null, utilise === null. Pour les tableaux, utilise Array.isArray(). Pour NaN, utilise Number.isNaN() (pas le isNaN() global qui convertit d'abord en nombre).---## 9. Boucle for avec var -- Le piege de la closureCe piege est un grand classique des entretiens techniques. Il combine deux difficultes : les closures et le hoisting de var.❌ Le code qui affiche 5 cinq fois :`javascriptfor (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000);}// Affiche : 5, 5, 5, 5, 5`✅ Le code qui affiche 0, 1, 2, 3, 4 :`javascriptfor (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000);}// Affiche : 0, 1, 2, 3, 4`Avec var, il n'existe qu'une seule variable i pour toute la boucle. Quand les callbacks setTimeout s'executent (apres la fin de la boucle), ils lisent tous la meme variable i, qui vaut alors 5. Avec let, chaque iteration de la boucle cree une nouvelle variable i avec sa propre portee de bloc. Chaque callback capture donc sa propre copie de i.---## 10. Oublier le break dans un switchLe dernier de notre liste des erreurs javascript debutant est subtil : le switch sans break fonctionne... mais pas comme tu le penses.❌ Le code qui traverse tout :`javascriptconst fruit = "banane";switch (fruit) { case "pomme": console.log("C'est une pomme"); case "banane": console.log("C'est une banane"); case "cerise": console.log("C'est une cerise"); default: console.log("Fruit inconnu");}// Affiche : "C'est une banane", "C'est une cerise", "Fruit inconnu"`✅ Le code avec break :`javascriptconst fruit = "banane";switch (fruit) { case "pomme": console.log("C'est une pomme"); break; case "banane": console.log("C'est une banane"); break; case "cerise": console.log("C'est une cerise"); break; default: console.log("Fruit inconnu");}// Affiche : "C'est une banane"`Sans break, JavaScript continue d'executer les case suivants, meme s'ils ne correspondent pas -- c'est le "fall-through". Ce comportement est herite du langage C et constitue l'une des sources de bugs les plus frequentes. Chaque case doit se terminer par break (ou return si tu es dans une fonction). Le fall-through volontaire est rare et doit etre commente pour etre clair.---## Bonus : 3 erreurs de niveau intermediaireTu maitrises les bases ? Voici trois pieges javascript supplementaires qui attrapent meme les developpeurs avec quelques mois d'experience.### B1. Shallow copy vs deep copy -- Le spread ne copie pas tout❌ Le code qui croit copier :`javascriptconst original = { nom: "Alice", scores: [10, 20, 30], adresse: { ville: "Paris" }};const copie = { ...original };copie.adresse.ville = "Lyon";copie.scores.push(40);console.log(original.adresse.ville); // "Lyon" -- modifie !console.log(original.scores); // [10, 20, 30, 40] -- modifie !`✅ Le code qui copie vraiment :`javascriptconst original = { nom: "Alice", scores: [10, 20, 30], adresse: { ville: "Paris" }};// Deep copy avec structuredClone (navigateurs modernes)const copie = structuredClone(original);copie.adresse.ville = "Lyon";copie.scores.push(40);console.log(original.adresse.ville); // "Paris" -- intactconsole.log(original.scores); // [10, 20, 30] -- intact`Le spread operator (...) ne fait qu'une copie superficielle (shallow copy) : seul le premier niveau est copie. Les objets et tableaux imbriques restent des references vers les originaux. Pour une copie profonde (deep copy), utilise structuredClone() (disponible dans tous les navigateurs modernes) ou JSON.parse(JSON.stringify(obj)) pour les objets simples.### B2. Event listener leak -- La fuite memoire silencieuse❌ Le code qui fuit :`javascriptfunction initialiserBouton() { const bouton = document.getElementById("monBouton"); bouton.addEventListener("click", function() { console.log("Clic !"); });}// Appele a chaque navigation dans une SPA// -> un nouveau listener a chaque fois, les anciens ne sont jamais supprimesinitialiserBouton();initialiserBouton();initialiserBouton(); // 3 listeners empiles !`✅ Le code qui nettoie :`javascriptfunction handleClick() { console.log("Clic !");}function initialiserBouton() { const bouton = document.getElementById("monBouton"); bouton.removeEventListener("click", handleClick); // nettoyage d'abord bouton.addEventListener("click", handleClick);}// Alternative moderne avec AbortControllerfunction initialiserBoutonV2() { const controller = new AbortController(); const bouton = document.getElementById("monBouton"); bouton.addEventListener("click", () => { console.log("Clic !"); }, { signal: controller.signal }); // Pour tout supprimer d'un coup plus tard : // controller.abort();}`Chaque appel a addEventListener ajoute un nouveau listener, meme si la meme fonction est deja attachee (dans le cas de fonctions anonymes). Dans une Single Page Application, cela cree des fuites memoire et des comportements erratiques (le handler s'execute plusieurs fois par clic). Toujours nettoyer avec removeEventListener ou utiliser un AbortController.### B3. Floating point : 0.1 + 0.2 !== 0.3❌ Le code mathematiquement incorrect :`javascriptconsole.log(0.1 + 0.2); // 0.30000000000000004console.log(0.1 + 0.2 === 0.3); // falseconst prix = 0.1 + 0.2;if (prix === 0.3) { console.log("Prix correct"); // jamais execute !}`✅ Le code qui gere la precision :`javascript// Solution 1 : comparer avec une marge d'erreur (epsilon)function estEgal(a, b) { return Math.abs(a - b) < Number.EPSILON;}console.log(estEgal(0.1 + 0.2, 0.3)); // true// Solution 2 : travailler en centimes (entiers)const prixEnCentimes = 10 + 20; // 30 centimesconst prixEnEuros = prixEnCentimes / 100; // 0.3console.log(prixEnEuros === 0.3); // true// Solution 3 : arrondir pour l'affichageconsole.log(parseFloat((0.1 + 0.2).toFixed(10))); // 0.3`JavaScript utilise la norme IEEE 754 pour representer les nombres a virgule flottante en binaire. Certaines fractions decimales comme 0.1 ne peuvent pas etre representees exactement en binaire, ce qui produit de minuscules erreurs d'arrondi. Ce n'est pas un bug de JavaScript : c'est le cas dans presque tous les langages. Pour les calculs financiers, travaille toujours en centimes (entiers).---## Tableau recapitulatif
| # | Erreur | Probleme | Solution |
|---|---|---|---|
| 1 | == au lieu de === | Conversion de type implicite | Toujours utiliser === et !== |
| 2 | var au lieu de let/const | Hoisting + portee fonction | const par defaut, let si reassignation |
| 3 | this qui change | Contexte perdu dans les callbacks | Arrow functions pour les callbacks |
| 4 | Mutation de tableau | Reference partagee | Spread operator [...arr] |
| 5 | return oublie dans .map() | Tableau de undefined | Return explicite ou syntaxe sans accolades |
| 6 | Synchrone vs asynchrone | Resultat pas encore disponible | async/await ou .then() |
| 7 | Comparaison d'objets | Compare les references, pas les valeurs | JSON.stringify ou comparaison manuelle |
| 8 | typeof null | Retourne "object" (bug historique) | === null, Array.isArray(), Number.isNaN() |
| 9 | Boucle for + var | Closure sur variable unique | Utiliser let dans la boucle |
| 10 | break oublie dans switch | Fall-through involontaire | break (ou return) apres chaque case |
| B1 | Shallow copy | Spread ne copie que le 1er niveau | structuredClone() pour le deep copy |
| B2 | Event listener leak | Listeners empiles, jamais supprimes | removeEventListener ou AbortController |
| B3 | 0.1 + 0.2 !== 0.3 | Precision flottante IEEE 754 | Travailler en centimes ou utiliser epsilon |
---## Les 3 regles d'or du JavaScript sans bugsApres avoir parcouru ces 13 erreurs, voici trois principes qui, a eux seuls, eliminent la majorite des bugs javascript courants.Regle 1 : Sois strict par defaut. Utilise ===, utilise const, utilise le mode strict ("use strict"). Chaque fois que JavaScript te laisse le choix entre une version permissive et une version stricte, choisis la stricte. Le permissif, c'est de la dette technique deguisee.Regle 2 : Ne mute jamais, copie toujours. Que ce soit un tableau, un objet ou une variable, prefere creer une nouvelle valeur plutot que modifier l'existante. C'est la base de la programmation fonctionnelle et c'est ce qui rend ton code previsible. Spread, map, filter, structuredClone -- ce sont tes meilleurs allies.Regle 3 : Teste dans la console, pas dans ta tete. Quand tu n'es pas sur du comportement d'une expression, ouvre la console de ton navigateur (F12) et teste-la. typeof null, 0.1 + 0.2, [] == false -- deux secondes dans la console t'eviteront deux heures de debugging. La documentation MDN JavaScript est aussi ta meilleure alliee pour verifier le comportement exact d'une fonction. Apprendre javascript, c'est avant tout experimenter.---## A toi de jouerCes erreurs ne sont pas des signes de faiblesse. Elles sont des etapes normales dans l'apprentissage de tout langage. Le simple fait que tu aies lu cet article jusqu'au bout te place deja devant la majorite des debutants qui reproduisent les memes bugs sans jamais comprendre pourquoi.La prochaine fois que tu tombes sur un undefined inattendu ou un true qui devrait etre false`, reviens consulter cette liste. Avec le temps, tu les reperereras avant meme d'executer ton code.Les javascript bonnes pratiques s'acquierent par la repetition. Plus tu codes, plus ces reflexes deviennent automatiques. Et si tu utilises React, decouvre aussi les erreurs specifiques aux hooks React que les debutants commettent le plus souvent. Tu peux aussi passer a React pour construire des interfaces modernes une fois les bases JavaScript bien solides.Envie de pratiquer ? GoGoKodo propose des ateliers interactifs JavaScript avec correction automatique -- du debutant a l'expert, 100% gratuit.