JavaScript peut sembler simple en surface, mais sous le capot, il se passe beaucoup de choses. Aujourd'hui, nous allons explorer certains concepts essentiels tels que les contextes d'exécution, le levage, les types de données primitifs et non primitifs et la conversion de type. Il est essentiel de les comprendre si vous souhaitez écrire un code meilleur et sans bug.
Contexte global d'exécution et environnement lexical
Lorsque vous exécutez un fichier JavaScript dans le navigateur, le code est exécuté ligne par ligne dans la pile d'appels. Cependant, avant l'exécution de votre code, un contexte d'exécution global est créé. Ce contexte configure les objets this et window. Dans Node.js, l'équivalent de window est global, et si vous comparez les deux, vous constaterez que window === global renvoie true.
Chaque fois que vous appelez une fonction, un nouvel environnement lexical est créé. Le contexte d'exécution global est le premier à être créé, et toutes les fonctions définies à l'intérieur peuvent accéder à ses variables. C'est ainsi que fonctionne la chaîne de portée de JavaScript : vous pouvez accéder aux variables dans la portée externe (globale) depuis l'intérieur d'une fonction.
Levage : Variables et fonctions
JavaScript dispose d'un mécanisme appelé levage, dans lequel les variables et les fonctions sont « déplacées » vers le haut de leur portée lors de la compilation. Voici comment cela fonctionne :
Variables : les variables déclarées avec var sont partiellement levées, ce qui signifie que vous pouvez les référencer avant qu'elles ne soient initialisées, mais leur valeur sera indéfinie jusqu'à ce que la ligne où elles sont initialisées soit atteinte.
Fonctions : les fonctions déclarées avec la syntaxe de déclaration de fonction sont entièrement hissées, ce qui signifie que vous pouvez appeler la fonction avant même sa déclaration dans le code.
Exemple :
console.log(a); // undefined var a = 5; console.log(b); // Error: b is not defined let b = 10; hoistedFunction(); // Works! function hoistedFunction() { console.log('This function is hoisted!'); } notHoistedFunction(); // Error: notHoistedFunction is not a function var notHoistedFunction = function() { console.log('This function is not hoisted!'); }
Comme vous pouvez le voir, let et const ne sont pas hissés comme var, et les expressions de fonction (comme notHoistedFunction) ne sont définies qu'au moment de l'exécution.
Types primitifs et non primitifs
JavaScript a deux types de données : primitives et non primitives.
Les types primitifs incluent chaîne, nombre, booléen, non défini, nul, symbole et bigint. Ceux-ci sont immuables, ce qui signifie que leurs valeurs ne peuvent pas être modifiées. Par exemple :
let x = 'hello'; x[0] = 'H'; // This won’t change the string, it stays 'hello'
Les types non primitifs sont des objets, des tableaux et des fonctions. Ceux-ci sont mutables et leurs valeurs peuvent être modifiées car elles sont transmises par référence. Par exemple :
let obj1 = { name: 'John' }; let obj2 = obj1; // Both obj1 and obj2 now reference the same object obj2.name = 'Doe'; console.log(obj1.name); // Outputs: Doe
Pour éviter de modifier l'objet original, vous pouvez créer une copie superficielle en utilisant Object.assign() ou l'opérateur spread (...). Pour les copies complètes, qui copient des objets imbriqués, utilisez JSON.parse() et JSON.stringify().
Exemple d'extrait de code : copie superficielle ou copie approfondie
// Shallow copy example let obj1 = { name: 'John', details: { age: 30 } }; let obj2 = { ...obj1 }; // Shallow copy obj2.details.age = 40; console.log(obj1.details.age); // Output: 40 (Shallow copy affects the original) // Deep copy example let obj3 = JSON.parse(JSON.stringify(obj1)); // Deep copy obj3.details.age = 50; console.log(obj1.details.age); // Output: 40 (Deep copy doesn’t affect the original)
Conversion et comparaison de types
JavaScript est un langage typé dynamiquement, ce qui signifie que vous n'avez pas besoin de spécifier explicitement les types de variables. Cependant, cela peut parfois conduire à un comportement inattendu, notamment lors de l'utilisation d'opérateurs de comparaison.
Préférez toujours utiliser des triples égaux (===) plutôt que des doubles égaux (==) pour éviter la coercition de type. Par exemple :
console.log(0 == '0'); // true (type coercion happens) console.log(0 === '0'); // false (no type coercion)
Pour les cas particuliers, comme comparer NaN, utilisez Object.is() car NaN === NaN renvoie false.
console.log(NaN === NaN); // false console.log(Object.is(NaN, NaN)); // true
Runtime de JavaScript et Node.js
JavaScript s'exécute sur un environnement d'exécution synchrone à thread unique, ce qui signifie qu'il ne peut exécuter qu'une seule tâche à la fois. Cela peut sembler limitant, mais JavaScript gère efficacement les tâches asynchrones en utilisant l'API Web et la file d'attente de rappel. Voici comment cela fonctionne :
Lorsque JavaScript rencontre une tâche asynchrone (comme setTimeout ou une requête HTTP), il envoie la tâche à l'API Web.
La pile d'appels continue d'exécuter le code restant.
Une fois la tâche asynchrone terminée, elle est ajoutée à la file d'attente de rappel et s'exécutera lorsque la pile d'appels sera vide.
Node.js étend ce runtime côté serveur, en utilisant le moteur V8 et un système d'E/S non bloquant alimenté par libuv. Node.js a introduit l'idée d'une boucle d'événements à thread unique capable de gérer plusieurs requêtes sans bloquer d'autres opérations.
En comprenant comment JavaScript gère les contextes d'exécution, le levage, la conversion de type et les tâches asynchrones, vous serez en mesure d'écrire du code plus propre et plus efficace. Grâce à la nature dynamique de JavaScript, des outils tels que TypeScript peuvent vous aider à éviter les pièges courants en fournissant des vérifications de type statiques qui préparent votre code à la production.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!