Javascript
Senior
Quiz-Javascript-Senior
Event-Loop
Microtasks
Javascript-Prototypes
Heritage-Js
Async-Await
Questions-Entretien-Javascript-Senior
Qcm-Javascript-Avance
Patterns-Es6
Closures-Avance
Front-End
Javascript-Avance
📝
Entretien JavaScript Senior — Event Loop et Prototypes
20 questions avancées JavaScript : event loop, microtasks, async/await, prototypes, closures complexes, patterns ES6+ et optimisations. Idéal pour réussir un entretien développeur Senior.
Banque de révision : les 40 questions corrigées
Voici l'intégralité des 40 questions de « Entretien JavaScript Senior — Event Loop et Prototypes », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.
Explication :
var est hoistée (déclaration remontée) et a une portée fonction. let et const ont une portée bloc (
{}). const empêche la réassignation mais permet la mutation des propriétés d'un objet.
Illustre avec un exemple de boucle :
for (var i…) vs for (let i…) pour montrer la différence concrète.
Explication :
typeof null retourne "object" — c'est un bug historique de JavaScript datant de 1995 qui n'a jamais été corrigé pour maintenir la rétrocompatibilité.
Ce piège est classique en entretien. Précise que
null === null est la bonne façon de tester un null.
Explication :
Une closure permet à une fonction interne de capturer les variables de son scope lexical parent. Même si la fonction externe a retourné, la fonction interne conserve une référence vivante à ces variables.
Donne l'exemple du compteur :
function counter() { let n = 0; return () => ++n; } — chaque appel à counter() crée un scope indépendant.
Explication :
[] + [] convertit les deux tableaux en chaînes ("" + "") et retourne une chaîne vide "". La coercition de type en JS est source de nombreux pièges.
Enchaîne avec
[] + {} → "[object Object]" et {} + [] → dépend du contexte. Bonne démonstration de maîtrise.
Explication :
En JS, chaque objet possède une référence interne
[[Prototype]] (accessible via Object.getPrototypeOf()). Quand on accède à une propriété, JS remonte la chaîne des prototypes jusqu'à trouver la propriété ou atteindre null.
Distingue bien prototype chain (héritage délégué) des classes ES6 qui sont du sucre syntaxique par-dessus ce même mécanisme.
Explication :
La call stack exécute d'abord le code synchrone (A, D). Les microtasks (Promise) passent avant les macrotasks (setTimeout) : C s'affiche avant B. Ordre final : A → D → C → B.
L'event loop est un sujet phare en entretien senior. Mentionne les notions de microtask queue (Promise, queueMicrotask) vs macrotask queue (setTimeout, setInterval, I/O).
Explication :
== (égalité lâche) applique la coercition de type : "5" == 5 est true. === (égalité stricte) compare valeur ET type sans conversion : "5" === 5 est false.
Recommande toujours
=== en production. Les exceptions légitimes à == sont rares — cite null == undefined comme exemple.
Explication :
.flat(depth) aplatit un tableau jusqu'à la profondeur spécifiée. flat(Infinity) aplatit complètement [1, [2, [3, [4]]]] → [1, 2, 3, 4], quelle que soit la profondeur d'imbrication.
Explication :
Un event listener conserve une référence à son callback ET à l'élément DOM. Si l'élément est retiré du DOM sans que le listener soit supprimé, il reste en mémoire. Il faut
removeEventListener(type, refFonction) — d'où l'importance d'utiliser des références nommées, pas des lambdas anonymes.
Cite aussi l'
AbortController (ES2021) comme alternative moderne pour supprimer plusieurs listeners en une fois.
Explication :
Object.freeze() rend un objet immuable (lecture seule). La tentative de modification est silencieusement ignorée en mode normal. En mode strict ("use strict"), elle lève une TypeError.
Explication :
Le currying transforme
f(a, b, c) en f(a)(b)(c). Utile pour l'application partielle : const add5 = add(5) crée une nouvelle fonction à partir d'une fonction générique. Facilite la composition fonctionnelle.
Donne un exemple concret :
const multiply = a => b => a * b; const double = multiply(2);
Explication :
Le spread operator
{ ...obj1, ...obj2 } crée un nouvel objet sans modifier les originaux (shallow copy). Object.assign(obj1, obj2) mute obj1 directement.
Explication :
undefined signifie qu'une variable a été déclarée mais pas encore initialisée. null est une valeur explicite qui représente "aucune valeur" — c'est un choix du développeur. typeof undefined === "undefined" et typeof null === "object".
Mentionne
null == undefined est true (coercition) mais null === undefined est false (strict).
Explication :
?? ne déclenche que pour null et undefined, contrairement à || qui déclenche pour toute valeur falsy. Ainsi : 0 ?? "défaut" retourne 0, mais 0 || "défaut" retourne "défaut".
Explication :
L'event delegation exploite le bubbling : un événement sur un enfant remonte jusqu'au parent. En attachant un listener unique sur le conteneur et en vérifiant
event.target, on évite des listeners multiples et on gère les éléments ajoutés dynamiquement.
Exemple : une liste de 1000 items — un seul listener sur
<ul>, pas 1000 listeners sur chaque <li>.
Explication :
WeakMap : clés objets uniquement, références faibles (l'objet peut être garbage collecté si aucune autre référence n'existe), non itérable. Map : toutes clés, références fortes, itérable. Ideal pour stocker des métadonnées privées associées à des objets.
Explication :
Toute fonction
async retourne une Promise implicitement. await "suspend" la fonction courante (sans bloquer le thread) jusqu'à la résolution de la Promise. Le code reprend dans la microtask queue. C'est une abstraction sur .then()/.catch().
Mentionne que le gestion d'erreurs se fait avec try/catch avec async/await, vs .catch() avec les Promises brutes.
Explication :
Number.isNaN() vérifie si la valeur est exactement NaN (sans coercition). 'hello' n'est pas NaN → false. La fonction globale isNaN() convertit d'abord en nombre : Number('hello') donne NaN → true.
Explication :
L'immutabilité signifie qu'on ne modifie pas les données en place mais qu'on crée de nouvelles valeurs. Avantages : comparaison par référence (
prev !== next dans React/Redux), historique des états, prévention des effets de bord. Outils : spread, Object.assign, Array.map/filter, Immer.js.
Lien direct avec la performance de React : si la référence n'a pas changé, pas de re-render. Montre ta connaissance des frameworks.
Explication :
user?.address?.city est équivalent à user && user.address && user.address.city. Si une étape est null ou undefined, l'expression court-circuite et retourne undefined sans erreur. Fonctionne aussi pour les appels de méthodes : obj.method?.().
Complète souvent avec
?? pour fournir une valeur par défaut : user?.name ?? "Anonyme".
Explication :
Avec
var, la variable i a une portée fonction (ou globale). Au moment où les callbacks setTimeout s'exécutent (après la boucle), i vaut déjà 3. D'où trois fois 3. Avec let, chaque itération capture une nouvelle liaison → 0, 1, 2.
Solution classique : IIFE
(function(i) { setTimeout(...) })(i) ou utiliser let.
Explication :
La TDZ est la période où une variable déclarée avec
let ou const existe (hoistée) mais n'est pas encore initialisée. Y accéder avant la déclaration lève une ReferenceError. var n'a pas de TDZ car initialisée à undefined.
Exemple :
{ console.log(x); let x = 1; } → ReferenceError.
Explication :
Object.seal() : empêche d'ajouter/supprimer des propriétés, mais on peut modifier les valeurs existantes. Object.freeze() : tout est bloqué (ajout/suppression/modification). En mode strict, les tentatives de modification lèvent une TypeError.
Explication :
JS utilise un GC à marquage et balayage (mark-and-sweep). Les racines sont les variables globales, les références sur la pile, etc. L'algorithme parcourt récursivement les références, marque les objets atteignables, puis supprime ceux qui ne le sont pas.
Les cycles de références ne sont plus un problème avec ce mécanisme. Mentionne les WeakMap/WeakSet pour éviter de bloquer le GC.
Explication :
Le spread operator
[...] fonctionne sur les itérables. Les chaînes sont itérables (charactère par charactère). ...'hello' donne "h","e","l","l","o" dans un tableau. C'est une alternative à String.split('').
Explication :
Une Promise est un proxy pour une valeur future. États : pending (initial), fulfilled (succès), rejected (erreur). Chaque
.then() ou .catch() retourne une nouvelle Promise, ce qui évite le callback hell et permet le chaînage fluide.
Mentionne que
Promise.all(), Promise.race(), Promise.allSettled() sont des outils avancés pour orchestrer plusieurs Promises.
Explication :
Object.create(null) crée un objet sans aucune propriété héritée (pas de toString, hasOwnProperty, etc.). Utile pour créer un dictionnaire "propre" ou une map sans collisions de clés héritées.
Contrairement à
{} qui hérite de Object.prototype.
Explication :
Les modules ES6 sont statically analyzable (imports/export déterminés à la compilation). Les exports sont des live bindings : si la valeur change dans le module source, l'import reflète le changement. Par défaut, les modules sont en mode strict automatiquement.
Distinction avec CommonJS (require) : les exports sont des copies, et require peut être appelé dynamiquement.
Explication :
Debouncing : exécute la fonction après un délai d'inactivité (idéal pour autocomplete). Throttling : garantit qu'une fonction ne s'exécute pas plus d'une fois par intervalle (idéal pour scroll, resize).
Donne des exemples : debounce pour un champ de recherche, throttle pour un écouteur de scroll.
Explication :
Les opérateurs de comparaison s'évaluent de gauche à droite.
1 < 2 → true, puis true < 3 → 1 < 3 → true. Donc résultat true. Piège : 3 > 2 > 1 → true > 1 → 1 > 1 → false.
Pour vérifier qu'un nombre est entre deux bornes :
2 < x && x < 5.
Explication :
L'event loop vide d'abord la call stack. Ensuite, elle exécute toutes les microtasks (Promise callbacks, queueMicrotask). Puis elle prend une macrotask (setTimeout, setInterval, I/O). Cycle infini. Priorité : synchrone > microtasks > macrotasks.
Exemple important :
setTimeout(fn, 0) n'est pas garanti à 0ms, mais minimum après la microtask queue.
Explication :
Un
Set stocke des valeurs uniques. L'initialisation avec un tableau itérable supprime automatiquement les doublons. Le résultat est un objet Set (itérable) contenant 1, 2, 3.
Technique courante pour dédoublonner un tableau :
[...new Set(tableau)].
Explication :
Les classes ES6 sont principalement du sucre syntaxique.
class A extends B équivaut à A.prototype = Object.create(B.prototype). Le mécanisme sous-jacent reste la chaîne de prototypes. Le constructeur parent s'appelle avec super().
Mentionne que les méthodes de classe sont définies sur le prototype (non énumérables), contrairement aux propriétés d'instance.
Explication :
typeof sur une fonction retourne "function". C'est un type spécial en JavaScript. Bien que les fonctions soient des objets (elles ont des propriétés et méthodes), typeof fait une exception pour elles.
Pour différencier une fonction d'un objet ordinaire, on utilise
typeof fn === "function".
Explication :
La mémoïsation stocke les résultats d'appels précédents dans un cache. Si la même entrée est fournie, on retourne le résultat mis en cache sans recalculer. Exemple : Fibonacci récursif passe de O(2^n) à O(n).
Implémentation simple :
function memoize(fn) { const cache = {}; return (...args) => cache[args] ??= fn(...args); }.
Explication :
some() : OR logique (existe-t-il au moins un ?). every() : AND logique (tous sont-ils ?). Les deux s'arrêtent tôt (early exit) dès que la condition est déterminée.
Exemple :
array.some(v => v > 10) ; array.every(v => v > 0).
Explication :
Un Proxy enveloppe un objet cible et définit des traps (pièges) pour intercepter des opérations comme lecture de propriété (
get), assignation (set), appel de fonction (apply), etc. Utilisé pour la validation, le logging, l'observabilité (Vue 3).
Exemple :
new Proxy(obj, { get(target, prop) { return prop in target ? target[prop] : "default"; } }).
Explication :
Chaque appel à
Symbol() crée un symbole unique et immuable, même si la description est identique. Deux symboles ne sont jamais égaux (=== ou ==). Utilisé pour créer des clés de propriétés uniques, non énumérables.
Pour obtenir le même symbole à partir d'une clé :
Symbol.for("id") (symboles globaux partagés).
Explication :
Syntaxe :
(function() { ... })(). L'IIFE crée un scope lexical isolé. Avant let/const, c'était la seule façon d'avoir des variables privées et d'éviter les conflits globaux. Aujourd'hui, les modules ES6 et les blocs avec let/const sont préférés.
Aujourd'hui utile dans certains patterns d'initialisation :
const app = (function() { return { init() {} }; })();.
Explication :
Array.from() crée un tableau à partir d'un objet itérable ou array-like. Un objet { length: 5 } est array-like. La fonction de mapping transforme chaque index en sa valeur. Résultat : [0, 1, 2, 3, 4].
Technique courante pour initialiser un tableau de taille n :
Array.from({ length: n }, (_, i) => i).