Questions et réponses d'entrevue JavaScript fréquemment posées
Maîtriser JavaScript augmente significativement vos chances de décrocher un poste de développeur. Examinons ensemble les questions souvent posées lors d'entretiens techniques portant sur JavaScript.
JavaScript se distingue comme l'un des langages de programmation les plus répandus dans le développement web. Sa polyvalence le rend apte au développement d'une grande variété d'applications.
Avant d'aborder les questions d'entretien, explorons les bénéfices qu'offre l'apprentissage de JavaScript.
JavaScript est un langage de programmation léger, interprété ou compilé à la volée. Il constitue l'un des piliers fondamentaux du World Wide Web. Il est fortement recommandé de vous familiariser avec les deux autres langages essentiels du web, si ce n'est pas déjà fait.
JavaScript a été initialement conçu pour le web, mais son utilisation s'est considérablement élargie. Grâce à des environnements comme Node.js ou Deno, il est désormais possible de l'exécuter sur presque toutes les plateformes.
Découvrons quelques-uns de ses atouts.
Avantages de JavaScript
- Il est facile de se lancer. L'apprentissage est accessible même sans aucune expérience préalable en codage.
- Il bénéficie d'une vaste communauté. Vous trouverez aisément de l'aide si vous rencontrez des difficultés.
- De nombreuses bibliothèques et frameworks sont développés en JavaScript, accélérant ainsi le développement d'applications.
- JavaScript permet le développement d'applications front-end, back-end, Android, iOS, etc. Son applicabilité est très large, bien qu'il excelle particulièrement dans le développement web.
Quels sont les types de données en JavaScript ?
Les types de données servent à stocker différents types d'informations. Ils varient d'un langage de programmation à l'autre. JavaScript en propose 8, que nous allons examiner.
- Nombre
- Chaîne de caractères
- Booléen
- Indéfini
- Nul
- BigInt
- Symbole
- Objet
Tous les types de données, à l'exception de l'objet, sont qualifiés de valeurs primitives et sont immuables.
Quelles sont les méthodes intégrées en JavaScript ?
Les méthodes intégrées à JavaScript diffèrent selon les types de données. Elles sont accessibles via le type de données correspondant. Voici quelques exemples de méthodes intégrées pour divers types de données et structures de données.
- Nombre
- Chaîne de caractères
- toLowerCase
- startsWith
- charAt
- Tableau
Il existe un grand nombre de méthodes intégrées pour chaque type de données. Vous pouvez consulter les documentations pour obtenir la liste complète des méthodes disponibles pour les différents types de données et structures de données.
Comment créer un tableau en JavaScript ?
Les tableaux sont des structures de données fondamentales en JavaScript. Ils peuvent contenir des données de n'importe quel type, étant donné que JavaScript est un langage dynamique. Voici comment créer des tableaux en JavaScript.
La création de tableaux peut se faire simplement en utilisant des crochets []. Cette approche est rapide et directe pour créer des objets.
// Tableau vide
const arr = [];
// Tableau avec des valeurs aléatoires
const randomArr = [1, "Un", true];
console.log(arr, randomArr);
Il est également possible de créer un tableau en utilisant le constructeur Array, bien que cette approche soit rarement privilégiée dans les projets courants.
// Tableau vide
const arr = new Array();
// Tableau avec des valeurs aléatoires
const randomArr = new Array(1, "Un", true);
console.log(arr, randomArr);
Les tableaux en JavaScript sont modifiables ; vous pouvez les adapter à vos besoins après leur création.
Comment créer un objet en JavaScript ?
Outre les tableaux, les objets représentent une autre structure de données essentielle en JavaScript. Ils sont utilisés pour stocker des paires clé-valeur. La clé doit être une valeur immuable, tandis que la valeur peut être de n'importe quel type. Voici comment créer des objets en JavaScript.
La création d'objets peut être effectuée à l'aide d'accolades {}. Cette méthode est simple et rapide.
// Objet vide
const object = {};
// Objet avec des valeurs aléatoires
const randomObject = { 1: 2, un: "Deux", true: false };
console.log(object, randomObject);
On peut également créer des objets en utilisant le constructeur Object. Cependant, cette approche est moins courante dans les projets classiques.
// Objet vide
const object = new Object();
// Objet avec des valeurs aléatoires
const randomObject = new Object();
randomObject[1] = 2;
randomObject["un"] = "Deux";
randomObject[true] = false;
console.log(object, randomObject);
Les objets JavaScript sont modifiables. Il est possible de les modifier après leur création, comme illustré dans le deuxième exemple.
Comment déboguer du code JavaScript ?
Le débogage de code peut s'avérer complexe et varie selon le langage de programmation et le projet. Voyons les méthodes courantes utilisées pour déboguer JavaScript.
1. Journalisation
L'utilisation de console.log à divers endroits du code permet d'identifier les erreurs. Le code cesse l'exécution des lignes suivantes en cas d'erreur dans la ligne précédente.
La journalisation est une ancienne méthode de débogage qui reste efficace pour les petits projets et est une technique courante dans de nombreux langages.
2. Outils de développement
JavaScript étant largement utilisé pour les applications web, la plupart des navigateurs proposent des outils de développement qui facilitent le débogage du code JavaScript.
L'une des méthodes les plus utilisées consiste à définir des points d'arrêt dans les outils de développement. Ces points d'arrêt suspendent l'exécution de JavaScript, fournissant des informations détaillées sur son état à ce moment précis.
En définissant plusieurs points d'arrêt à proximité de zones susceptibles de contenir des erreurs, il est possible de déterminer l'origine du problème. C'est une approche très efficace pour déboguer les applications web JavaScript.
3. IDE
Les IDE peuvent également être utilisés pour déboguer du code JavaScript. VS Code, par exemple, prend en charge le débogage avec des points d'arrêt. La fonctionnalité de débogage peut varier selon l'IDE utilisé, mais la plupart intègrent cette fonctionnalité.
Comment intégrer du code JavaScript dans un fichier HTML ?
Le code JavaScript peut être ajouté à un fichier HTML via la balise script. Voici un exemple illustratif.
<!DOCTYPE html>
<html lang="fr">
<head>
<title>toptips.fr</title>
</head>
<body>
<h1>toptips.fr</h1>
<script>
// Le code JavaScript est inséré ici
console.log("Ceci est du code JavaScript");
</script>
</body>
</html>
Que sont les cookies ?
Les cookies sont des paires clé-valeur utilisées pour stocker de petites quantités d'informations, qui peuvent être de toute nature. Il est possible de définir une date d'expiration pour un cookie, après laquelle il sera supprimé. Ils sont couramment utilisés pour stocker les données des utilisateurs.
Les cookies ne sont pas supprimés lors de l'actualisation d'une page et persistent jusqu'à leur suppression ou expiration. Ils peuvent être inspectés dans les outils de développement des navigateurs, pour toute application web.
Comment lire un cookie ?
La lecture d'un cookie en JavaScript s'effectue via la propriété document.cookie. Cette action retourne tous les cookies créés.
console.log("Tous les cookies", document.cookie);
Cette fonction retournera une chaîne vide en l'absence de cookies.
Comment créer et supprimer un cookie ?
La création de cookies s'effectue en assignant une paire clé-valeur à la propriété document.cookie. Voici un exemple.
document.cookie = "un=Un;";
Dans cette syntaxe, "un" est la clé du cookie et "Un" est sa valeur. Il est possible d'ajouter des attributs au cookie, comme le domaine, le chemin, la date d'expiration, etc. Ces attributs sont séparés par des points-virgules (;). Tous les attributs sont optionnels.
Voici un exemple avec attributs.
document.cookie = "un=Un;expires=Jan 31 2023;path=/;";
Dans le code ci-dessus, une date d'expiration et un chemin d'accès ont été définis pour le cookie. Sans date d'expiration, le cookie est supprimé après la session. Le chemin par défaut correspond au chemin du fichier. La date d'expiration doit respecter le format GMT.
Voici comment créer plusieurs cookies.
document.cookie = "un=Un;expires=Jan 31 2023;path=/;";
document.cookie = "deux=Deux;expires=Jan 31 2023;path=/;";
document.cookie = "trois=Trois;expires=Jan 31 2023;path=/;";
Les cookies ne seront pas écrasés si la clé ou le chemin est différent lors de la création de plusieurs cookies. Si la clé et le chemin sont identiques, le cookie précédent sera écrasé. L'exemple suivant écrase le cookie précédemment défini.
document.cookie = "un=Un;expires=Jan 31 2023;path=/;";
document.cookie = "un=Deux;path=/;";
La date d'expiration a été supprimée du cookie et sa valeur modifiée.
Lors de tests, définissez une date d'expiration future pour vérifier que le code fonctionne correctement. Si la date reste fixée au 31 janvier 2023, même après cette date, les cookies ne seront pas créés.
La création et la mise à jour des cookies ont été abordées. Voyons maintenant comment les supprimer.
La suppression des cookies est simple. Il suffit de définir une date d'expiration passée pour le cookie. Voici un exemple.
// Création de cookies
document.cookie = "un=Un;expires=Jan 31 2023;path=/;";
document.cookie = "deux=Deux;expires=Jan 31 2023;path=/;";
document.cookie = "trois=Trois;expires=Jan 31 2023;path=/;";
// Suppression du dernier cookie
document.cookie = "trois=Trois;expires=Jan 1 2023;path=/;";
Le dernier cookie ne sera pas visible dans la liste des cookies, car il a été supprimé par la dernière ligne de code. Ceci conclut le guide sur les cookies.
Quels sont les différents frameworks JavaScript ?
Il existe de nombreux frameworks JavaScript, notamment React, Vue et Angular pour le développement d'interfaces utilisateur, ou encore Express, Koa et Nest pour le développement côté serveur. NextJS et Gatsby sont utilisés pour la génération de sites statiques, tandis que React Native et Ionic sont dédiés au développement d'applications mobiles. Cette liste n'est pas exhaustive. L'exploration de tous les frameworks existants prendrait beaucoup de temps ; il est conseillé de les explorer en fonction des besoins.
Fermetures en JavaScript
Une fermeture est une fonction associée à sa portée lexicale et à son environnement lexical parent. Elle permet d'accéder à des données provenant de portées externes. Les fermetures se forment lors de la création des fonctions.
function externe() {
const a = 1;
function interne() {
// Il est possible d'accéder à toutes les données de la portée de la fonction externe ici
// Ces données restent accessibles même si la fonction interne est exécutée en dehors de la fonction externe
// car la fermeture de interne est formée au moment de sa création
console.log("Accéder à a dans interne", a);
}
return interne;
}
const fonctionInterne = externe();
fonctionInterne();
Les fermetures sont largement utilisées dans les applications JavaScript, et il est possible que vous les ayez déjà utilisées sans en être conscient. Il reste beaucoup à apprendre sur ce concept, assurez-vous de bien le maîtriser.
Hoisting en JavaScript
Le hoisting est un mécanisme JavaScript qui consiste à déplacer les déclarations de variables, de fonctions et de classes vers le haut de la portée avant l'exécution du code.
// Accéder à `nom` avant sa déclaration
console.log(nom);
// Déclaration et initialisation de `nom`
var nom = "toptips.fr";
L'exécution du code ci-dessus ne générera pas d'erreur. Dans la plupart des langages, cela conduirait à une erreur. La sortie sera "undefined" car le hoisting se contente de déplacer les déclarations vers le haut, sans procéder à l'initialisation (qui sera effectuée à la ligne 3).
Si l'on remplace "var" par "let" ou "const", l'exécution du code suivant produira une erreur.
// Accéder à `nom` avant sa déclaration
console.log(nom);
// Déclaration et initialisation de `nom`
const nom = "toptips.fr";
Une erreur de référence indiquant qu'il est impossible d'accéder à la variable avant son initialisation sera alors levée.
ReferenceError: Impossible d'accéder à 'nom' avant l'initialisation
Les mots clés "let" et "const" ont été introduits avec ES6. Ils empêchent l'accès aux variables avant leur initialisation, comme le signale l'erreur. Les variables déclarées avec "let" ou "const" se trouvent dans une "zone morte temporelle" (TDZ) jusqu'à ce que leur ligne d'initialisation soit atteinte. L'accès aux variables de la TDZ est impossible.
Currying en JavaScript
Le currying est une technique qui transforme une fonction à plusieurs paramètres en une série de fonctions à un seul paramètre. Elle permet de convertir une fonction callable add(a, b, c, d) en add(a)(b)(c)(d). Voici un exemple pour illustrer ce processus.
function getCurryCallback(callback) {
return function (a) {
return function (b) {
return function (c) {
return function (d) {
return callback(a, b, c, d);
};
};
};
};
}
function add(a, b, c, d) {
return a + b + c + d;
}
const curriedAdd = getCurryCallback(add);
// Appel de curriedAdd
console.log(curriedAdd(1)(2)(3)(4));
Il est possible de généraliser la fonction getCurryCallback pour qu'elle puisse être appliquée à différentes fonctions. Pour en savoir plus, vous pouvez vous référer à JavaScript Info.
Différence entre document et window
L'objet window est l'objet racine du navigateur. Il contient des informations sur la fenêtre du navigateur, telles que l'historique, l'emplacement, le navigateur, etc. Il est globalement accessible en JavaScript. Il est possible de l'utiliser directement sans importation préalable. Les propriétés et les méthodes de l'objet window sont accessibles sans passer par window.
L'objet document est une composante de l'objet window. Tout le code HTML chargé sur une page web est converti en un objet document. Cet objet fait référence à l'élément spécial HTMLDocument et contient des propriétés et des méthodes spécifiques, à l'instar de tous les éléments HTML.
En résumé, l'objet window représente la fenêtre du navigateur, tandis que l'objet document représente le document HTML chargé dans cette fenêtre.
Différence entre côté client et côté serveur
Le côté client fait référence à l'utilisateur final qui interagit avec l'application, tandis que le côté serveur fait référence au serveur web sur lequel l'application est déployée.
Dans le contexte du développement front-end, le navigateur sur l'ordinateur de l'utilisateur est considéré comme le côté client et les services cloud comme le côté serveur.
Différence entre innerHTML et innerText
innerHTML et innerText sont des propriétés des éléments HTML qui permettent de modifier leur contenu.
innerHTML permet d'assigner une chaîne HTML qui sera interprétée comme du code HTML. Voici un exemple.
const titleEl = document.getElementById("title");
titleEl.innerHTML = '<span style="color:orange;">toptips.fr</span>';
En ajoutant un élément avec l'identifiant "title" dans votre code HTML, et en exécutant le script ci-dessus, vous verrez "toptips.fr" affiché en orange. Si vous inspectez l'élément, il sera inclus dans une balise span. innerHTML interprète donc la chaîne HTML pour la rendre comme du HTML standard.
À l'inverse, innerText prend une chaîne de caractères normale et l'affiche telle quelle, sans interprétation HTML. Si vous remplacez innerHTML par innerText dans le code ci-dessus, vous constaterez un résultat différent.
const titleEl = document.getElementById("title");
titleEl.innerText="<span style="color:orange;">toptips.fr</span>";
La chaîne de caractères fournie sera affichée telle quelle sur la page web.
Différence entre let et var
Les mots-clés "let" et "var" servent à déclarer des variables en JavaScript. Le mot-clé "let" a été introduit avec ES6.
"let" a une portée de bloc, tandis que "var" a une portée de fonction.
{
let a = 2;
console.log("Dans le bloc", a);
}
console.log("En dehors du bloc", a);
L'exécution du code ci-dessus génèrera une erreur à la dernière ligne, car l'accès à "let a" est limité à l'intérieur du bloc. Si l'on remplace "let" par "var", l'exécution ne lèvera pas d'erreur.
{
var a = 2;
console.log("Dans le bloc", a);
}
console.log("En dehors du bloc", a);
Il est possible d'accéder à une variable déclarée avec "var" en dehors du bloc. En remplaçant le bloc par une fonction, voici le résultat.
function exemple() {
var a = 2;
console.log("Dans la fonction", a);
}
exemple();
console.log("En dehors de la fonction", a);
L'exécution du code précédent générera une erreur de référence, car l'accès à "var a" est limité à la fonction.
Les variables déclarées avec "var" peuvent être redéclarées, ce qui n'est pas possible avec "let". Voici un exemple.
var a = "toptips.fr";
var a = "Chandan";
console.log(a);
let a = "toptips.fr";
let a = "Chandan";
console.log(a);
Le premier bloc de code ne générera pas d'erreur et la valeur de "a" sera remplacée par la dernière valeur assignée. Le second bloc de code générera une erreur car il n'est pas possible de redéclarer des variables à l'aide de "let".
Différence entre le stockage de session et le stockage local
Le stockage de session et le stockage local permettent de stocker des informations sur l'ordinateur des utilisateurs sans nécessiter de connexion internet. Ils permettent de stocker des paires clé-valeur. Les clés et les valeurs seront converties en chaînes de caractères si des types de données ou des structures de données différentes sont fournies.
Le stockage de session est effacé lorsque la session prend fin (à la fermeture du navigateur). Le stockage local, en revanche, n'est pas effacé tant que l'utilisateur ne le fait pas manuellement.
L'accès, la mise à jour et la suppression du stockage de session et du stockage local s'effectuent via les objets sessionStorage et localStorage respectivement.
Qu'est-ce que NaN en JavaScript ?
NaN est l'abréviation de Not-a-Number (Pas un nombre). Cela signifie qu'une valeur n'est pas un nombre valide en JavaScript. Des opérations comme 0/0, undefined * 2, 1 + undefined ou null * undefined conduiront à une sortie NaN.
Qu'est-ce que la portée lexicale ?
La portée lexicale fait référence à l'accès aux variables à partir de la portée de leurs parents. Si nous avons une fonction avec deux fonctions internes, la fonction la plus interne peut accéder aux variables des portées de ses deux fonctions parentes. De même, la fonction du 2ème niveau peut accéder à la portée de la fonction la plus externe. Voici un exemple illustratif.
function plusExterne() {
let a = 1;
console.log(a);
function milieu() {
let b = 2;
// `a` est accessible ici
console.log(a, b);
function plusInterne() {
let c = 3;
// `a` et `b` sont accessibles ici
console.log(a, b, c);
}
plusInterne();
}
milieu();
}
plusExterne();
JavaScript utilise une chaîne de portée pour trouver la variable lors de son accès dans le code. Il vérifie d'abord la variable dans la portée actuelle, puis dans la portée parente, jusqu'à la portée globale.
Qu'est-ce que le passage par valeur et le passage par référence ?
Le passage par valeur et le passage par référence sont deux méthodes pour passer des arguments à une fonction en JavaScript.
Passage par valeur : une copie des données d'origine est créée et transmise à la fonction. Ainsi, les modifications apportées dans la fonction n'affectent pas les données d'origine. Voici un exemple.
function exemple(a) {
// Modification de la valeur de `a`
a = 5;
console.log("Dans la fonction", a);
}
let a = 3;
exemple(a);
console.log("En dehors de la fonction", a);
La valeur de "a" à l'extérieur de la fonction reste inchangée, même après sa modification dans la fonction.
Passage par référence : la référence des données est transmise à la fonction. Par conséquent, les modifications apportées à la fonction modifient également les données d'origine.
function exemple(arr) {
// Ajout d'une nouvelle valeur au tableau
arr.push(3);
console.log("Dans la fonction", arr);
}
let arr = [1, 2];
exemple(arr);
console.log("En dehors de la fonction", arr);
La modification de "arr" dans la fonction affecte sa valeur d'origine.
Note : tous les types de données primitifs sont passés par valeur et les types de données non primitifs sont passés par référence.
Qu'est-ce que la mémorisation ?
La mémorisation est une technique qui consiste à stocker des valeurs déjà calculées dans un cache pour les réutiliser ultérieurement sans avoir à les recalculer. Cela permet d'accélérer l'exécution du code, en particulier pour des calculs complexes. Un compromis existe en matière de stockage, mais il est souvent négligeable par rapport au gain de temps.
const memo = {};
function add(a, b) {
const key = `${a}-${b}`;
// Vérification de l'existence de la valeur dans le cache
if (memo[key]) {
console.log("Calcul évité");
return memo[key];
}
// Ajout de la nouvelle valeur calculée dans le cache
// Le cache est un simple objet global ici
memo[key] = a + b;
return memo[key];
}
console.log(add(1, 2));
console.log(add(2, 3));
console.log(add(1, 2));
Il s'agit d'un exemple simple de mémorisation. Bien que l'addition de deux nombres ne soit pas un calcul complexe, il sert à illustrer le principe.
Quel est le paramètre rest ?
Le paramètre rest est utilisé pour collecter tous les paramètres restants d'une fonction. Imaginons que nous avons une fonction qui nécessite au minimum deux arguments, mais qui peut en accepter davantage. Dans ce cas, on peut récupérer les deux premiers paramètres à l'aide de variables ordinaires, et utiliser le paramètre rest pour collecter les autres paramètres, à l'aide de l'opérateur rest.
function exemple(a, b, ...rest) {
console.log("Paramètre rest", rest);
}
exemple(1, 2, 3, 4, 5);
Dans l'exemple ci-dessus, le paramètre rest sera un tableau contenant les trois derniers arguments. Le paramètre rest permet de rendre le nombre d'arguments d'une fonction variable.
Une fonction ne peut avoir qu'un seul paramètre rest, qui doit obligatoirement être le dernier dans l'ordre des paramètres.
Qu'est-ce que la déstructuration d'objet ?
La déstructuration d'objet permet d'accéder aux variables d'un objet et de les affecter à des variables portant les mêmes noms que les clés de l'objet. Voici un exemple.
const object = { a: 1, b: 2, c: 3 };
// Déstructuration de l'objet
const { a, b, c } = object;
// a, b et c sont maintenant utilisables comme variables
console.log(a, b, c);
Il est possible de renommer les variables déstructurées sur la même ligne, comme suit.
const object = { a: 1, b: 2, c: 3 };
// Changement des noms de `a` et `b`
const { a: aModifie, b: bModifie, c } = object;
// aModifie, bModifie et c sont utilisables comme variables
console.log(aModifie, bModifie, c);
Qu'est-ce que la déstructuration de tableau ?
La déstructuration de tableau permet d'accéder aux variables d'un tableau et de les assigner à des variables. Voici un exemple.
const tableau = [1, 2, 3];
// Déstructuration du tableau
// Basée sur l'index du tableau
const [a, b, c] = tableau;
// Il est possible d'utiliser a, b et c comme variables
console.log(a, b, c);
Qu'est-ce que la capture d'événements et le bouillonnement d'événements ?
La capture d'événements et le bouillonnement d'événements sont deux modes de propagation d'événements dans le DOM HTML. Imaginez deux éléments HTML, l'un étant imbriqué dans l'autre. Un événement se produit sur l'élément interne. Le mode de propagation des événements détermine l'ordre dans lequel les gestionnaires d'événements sont exécutés.
Bouillonnement d'événements : Le gestionnaire d'événements de l'élément concerné est exécuté en premier, suivi par le gestionnaire de son élément parent et ainsi de suite, jusqu'à atteindre l'élément racine. C'est le comportement par défaut de tous les événements.
Capture d'événements : ce mode de propagation doit être spécifié lors de l'ajout d'un écouteur d'événements. L'exécution des événements s'effectuera dans l'ordre suivant si la capture d'événements est activée :
- Les événements commencent à s'exécuter depuis l'élément racine jusqu'à atteindre l'élément cible.
- L'événement de l'élément cible est exécuté à nouveau.
- La propagation en mode bouillonnement reprend depuis l'élément cible jusqu'à l'élément racine.
Il est possible d'arrêter la propagation d'un événement via la méthode event.stopPropogation dans le gestionnaire d'événements.
Quelles sont les Promesses en JavaScript ?
L'objet Promise est utilisé pour les opérations asynchrones dont le résultat (succès ou échec) sera connu ultérieurement.
Une promesse peut être dans l'un des états suivants :
- en attente : l'opération est en cours.
- résolue : l'opération a abouti avec succès. Les résultats sont accessibles via cet état.
- rejetée : l'opération a échoué. La raison (erreur) de cet échec est accessible.
Voici deux exemples pour illustrer le succès et l'échec d'une promesse.
// Promesse qui se terminera avec succès
const promesseReussie = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ message: "Terminé avec succès" });
}, 300);
});
promesseReussie
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
// Promesse qui se terminera en échec
const promesseEchouee = new Promise((resolve, reject) => {
setTimeout(() => {