Questions d’entretien d’embauche sur JavaScript

Introduction

Les postes de développeur full-stack et de développeur front-end sont les deux types de profils les plus recherchés à l’heure actuelle en programmation. Cela signifie qu’il y a une pression croissante sur les recruteurs et les managers responsable de l’embauche pour trouver des personnes possédant les compétences requises pour ces postes. Un recruteur qui doit interroger des candidats pour un poste de développeur JavaScript doit être apte à mesurer les capacités techniques de ces derniers. 

L’article qui suit rassemble les questions d’entretien d’embauche les plus courantes sur le langage de programmation JavaScript, et les réponses que la plupart des candidats à ces postes devraient être en mesure de fournir.

Nous avons réparti les questions en trois catégories. La première catégorie est celle des questions qui peuvent être posées au cours d’une conversation téléphonique, car elles ne nécessitent que des réponses verbales. N’importe quel développeur full-stack ou front-end devrait être en mesure d’y répondre sans trop de difficulté.

Le deuxième groupe de questions est semblable au premier en le sens que leurs réponses peuvent être verbales. Il est toutefois plus difficile d’y répondre que pour le premier groupe, car elles exigent une compréhension plus approfondie du langage Javascript. On peut les poser au cours d’un entretien téléphonique si le candidat est parvenu à répondre aux questions du premier groupe.

Enfin, la troisième catégorie est constituée de questions conçue pour une entrevue en personne lors de laquelle le candidat peut écrire ses réponses (sur un tableau, sur un ordinateur, …). Elles ont été pensées de manière à prouver qu’au-delà des connaissances de base, le candidat que vous interrogez dispose des atouts qui font la différence. 

Questions préalables

Quelle est la différence entre undefined et null en Javascript ?

À première vue, les valeurs undefined et null semblent similaires, mais elles sont en réalité très différentes. Tout d’abord, elles sont d’un type d’un différent :

1
2
3
4
5
> typeof(undefined)
"undefined"
 
> typeof(null)
"object"

null est une valeur qui peut être assignée à une variable et signifie « no value ». Cependant, les variables qui ont été déclarées mais qui n’ont pas encore reçu de valeur sont undefined. Ceci est vrai non seulement pour les variables mais aussi pour les clés de tableau et les membres d’un objet qui n’existent pas, les paramètres de fonction qui n’ont pas été donnés et le résultat de l’appel d’une fonction qui ne retourne pas de valeur.

En résumé, “null” est la valeur qui signifie « aucune valeur » et undefined est le type de variables qui n’ont pas reçu de valeur.

Quelle est la différence entre == et === ?

La réponse à cette question est assez simple, une réponse incorrecte d’un candidat devrait donc être considérée comme un véritable signal d’alarme. La réponse est que == effectue toutes les conversions de type nécessaires avant de faire une comparaison alors que === ne le fait pas. La table d’égalité Javascript est “tristement célèbre” et il est convenu d’utiliser === pour comparer les valeurs d’égalité.

Que fait la directive use strict ?

Le but de la directive use strict ; est de s’assurer que le code est exécuté en mode strict. Vous vous demandez probablement ce qu’est le « mode strict » ? Le mode strict aide les développeurs à éviter certains écueils en JavaScript. L’activation du mode strict signifie que la commande suivante entraînera une erreur.

  • Ne pas déclarer une variable. Cela permet d’éviter de mal taper le nom d’une variable car la variable mal tapée apparaîtra comme une nouvelle variable non déclarée.
  • Supprimer une variable, c’est-à-dire delete x;
  • Ecrire dans une propriété en lecture seule (les propriétés en lecture seule sont celles définies via Object.defineProperty()).
  • Utiliser des variables telles que arguments ou eval
  • Utilisation de l’instruction with

Expliquez ce qu’est un callback (fonction de retour) et fournissez un exemple simple

Un callback est une fonction passée en argument dans une autre fonction et est appelée lorsque la fonction (celle dans laquelle le callback est passé) est terminée.

Voici un exemple d’utilisation générique des callbacks.

1
2
3
4
5
6
7
8
function doIt(onSuccess, onFailure) {
    var err = ...
    if (err) {
      onFailure(err)
    } else {
      onSuccess()
    }
  }

Toute mention de l’utilisation des rappels avec Promise (voir ci-dessous) est un bon signe et doit être considéré en tant que tel.

Qu’est-ce qu’une pure function (fonction pure) et pourquoi est-elle si importante ?

Les pure functions sont la pierre angulaire de la programmation fonctionnelle qui est devenue de plus en plus populaire au fil des ans. Programmer avec des fonctions pures est souhaitable car elles sont plus faciles à raisonner puisqu’il n’y a pas de contexte à prendre en compte lors de leur utilisation ou de leur débugage. Il simplifie également le refactoring (réusinage) puisque les fonctions pures peuvent être déplacées (ou supprimées) sans affecter le comportement du programme.

Questions approfondies

Qu’est-ce qu’une closure (fermeture) ?

Les closures donnent lieu à ce que l’on appelle la « transparence référentielle » et des points bonus peuvent être attribués à un candidat s’il fait mention de ce terme.

D’après Wikipedia :

…une fermeture ou clôture (en anglais: closure) est une fonction accompagnée de son environnement lexical.

Une fermeture peut être difficile à définir, mais toute mention du terme « fonction » accompagnée de « environnement » ou « portée » peut être considérée comme étant une bonne réponse.

Une fermeture offre à une fonction l’accès à des variables qui lui seraient indisponibles sans cette fermeture.

Il est devenu commun d’envelopper le contenu d’un fichier Javascript dans une fonction. Quel est l’intérêt de cette pratique ?

Cette technique a été utilisée pour créer une sorte de namespace (espace de noms) pour un module afin que les variables définies dans ce module soient privées et n’entrent donc pas en conflit avec les variables de l’espace de noms global ou des autres modules.

Voici un exemple:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
var queue = (function () {
 
    var items = [];
   
    return {
      enqueue: function (item) {
        items.push(item);
      },
   
      dequeue: function () {
        if (items.length > 0) {
          var next = items[0];
          items.shift();
          return next;
        } else {
           throw "Cannot dequeue from an empty queue";
        }
      }
    };
  })(); 

Dans l’implémentation ci-dessus d’une structure de données de file d’attente, les éléments variables sont privés à l’implémentation et ne sont pas visibles ou modifiables par les clients d’une file.

Cette technique est connue sous le nom de « Immediately Invoked Function Expression » ou IIFE. Vous pouvez en savoir plus à ce sujet sur le Mozilla Developer Network et sur Wikipedia.

Cette technique peut être généralement utile pour le masquage de données et l’espacement des noms, mais les bundlers Javascript modernes comme le webpack sont devenus populaires pour obtenir le même résultat.

A quoi servent les promises (promesses) ?

Les promises sont utilisées pour la programmation asynchrone. Elles permettent aux programmes Javascript d’être écrits d’une manière non-bloquante comme le font les rappels normaux, mais sans la surcharge mentale induite par cette technique.

Les gens se réfèrent souvent à « l’enfer du rappel », qui se produit souvent lorsqu’on utilise les rappels pour atteindre l’asynchronie. Les promises ont été conçues pour atténuer ce problème.

Donnez un exemple de combinaison de plusieurs promises

Il y a plusieurs façons de combiner des promises. La plus importante (et celle que tout candidat doit absolument connaître) est la méthode then() d’une promise. C’est ainsi que les promesses sont séquencées, c’est-à-dire que les fonctions qui renvoient les promesses doivent être appelées sur des valeurs contenues dans des promesses précédemment exécutées.

Une autre méthode est Promise.all() qui est utilisée pour créer une seule promise à partir de plusieurs promesses indépendantes. Dans ce cas, la nouvelle promise est résolue lorsque toutes les promises données ont été résolues.

Enfin, il y a Promise.race() qui est similaire à Promise.all() en le sens qu’il crée une promise à partir de plusieurs autres mais diffère en ce que la nouvelle promise est résolue quand une seule des promesses données est résolue, notamment celle qui est résolue en premier.

A quoi servent les keywords (mots-clefs) async / await ?

Les promises ont réussi à alléger les problèmes associés à une solution de rappel strict à la programmation asynchrone, mais elles peuvent être difficiles à utiliser quand beaucoup de promesses sont impliquées et doivent être séquencées avec then(), tout comme le fait d’avoir à gérer les erreurs associées à tous ces chaînages.

Les mots-clés async / await sont utilisés pour permettre aux développeurs d’écrire du code qui ressemble beaucoup au (désuet) code impératif, mais qui est toujours asynchrone.
Les candidats doivent absolument savoir quand il est approprié et possible d’utiliser ces mots-clés. En particulier, ils doivent savoir que async ne peut être placé qu’avant le mot-clé function sur une définition de fonction. Ceci marque la fonction comme retournant une promise. Le mot-clef await ne peut être utilisé qu’à l’intérieur d’une telle fonction asynchrone et doit être placé avant un appel à une fonction qui retournera une promise.

Questions à effectuer sur ordinateur ou sur papier

Ecrivez du code permettant de convertir un tableau de chaînes de caractères en un tableau de la longueur de ces chaînes

Cela devrait être un exercice relativement facile pour quelqu’un de familier avec Javascript. Une solution idéale à ce problème devrait ressembler à ceci :

    
1
2
3
var words = ["the", "quick", "brown", "fox"];
var wordLengths = words.map(word => word.length);
// wordLengths = [3, 5, 5, 3]

Une autre solution pourrait être l’utilisation de forEach:

    
1
2
var wordLengths2 = [];
words.forEach(word => wordLengths2.push(word.length));

Enfin, l’utilisation d’une boucle for permettrait d’obtenir un résultat similaire :

1
var wordLengths3 = []; var i; for (i = 0; i ❰ words.length; i++) { var word = words[i]; wordLengths3.push(word.length); }

Ecrivez du code permettant d’additionner un tableau de nombres

Un bon candidat devrait être capable d’apporter une solution de ce type :

    
1
2
3
const nums = [1, 2, 3, 4, 5];
const summer = (a, b) => a + b;
const sum = nums.reduce(summer, 0);

Comme pour la question précédente, ce problème peut également être résolu en utilisant une boucle for ou un forEach, mais ces réponses doivent également être considérées comme moins satisfaisantes.

Cet exercice technique permet de rebondir sur quelques autres bonnes questions :

A quoi sert la fonction summer et quelles sont les valeurs qui lui sont transmises ?

Le candidat doit être capable d’expliquer que la fonction summer (également appelée fonction réductrice) est appelée pour chaque élément du tableau et que la première valeur qui lui est passée est un accumulateur et le second argument est l’élément courant du tableau. L’accumulateur est le résultat de l’appel à la fonction de réduction pour l’élément précédent du tableau. On peut faire preuve d’une certaine indulgence à l’égard des candidats qui pourraient voir l’ordre de ces deux arguments inversé, car on sait que des langages différentes utilisent l’ordre inverse.

Si l’accumulateur contient le résultat de la fonction de réduction de l’élément de tableau précédent, quelle est sa valeur pour le premier élément de tableau ?

Un développeur Javascript compétent saura que c’est précisément à cela que sert le deuxième argument reduce: la valeur initiale de l’accumulateur utilisé pour réduire le premier élément du tableau.

Le deuxième argument reduce est facultatif. Que se passe-t-il s’il n’est pas présent ?

Dans ce cas, le premier élément du tableau est utilisé comme valeur initiale et la réduction continue avec le deuxième élément du tableau.

Enfin, vous pouvez demander :

Que se passe-t-il dans le cas où reduce est appelé sur un tableau vide sans fournir la valeur initiale optionnelle de l’accumulateur ?

Cette situation entraîne une erreur. Le candidat au poste peut s’étendre sur ce point et dire qu’une valeur initiale doit toujours être fournie, ou bien qu’il faut être assuré le tableau n’est pas vide.

Ecrivez une fonction pouvant être appelée ainsi : greeter(« Hello »)(« Candidate ») et connectera « Hello, Candidate! » à la console

Les candidats à l’esprit aiguisé reconnaîtront cette technique comme une curryfication dans laquelle les fonctions sont capables d’accepter moins que le nombre total d’arguments. L’appel d’une telle fonction est appelé application partielle.

Ils peuvent également commenter qu’il s’agit d’une fonction d’ordre supérieur (aussi appelée HOF) puisqu’elle est implémentée comme une fonction renvoyant une autre fonction.

Une implémentation s’apparenterait à ceci :

    
1
2
3
4
5
function greeter(greeting) {
    return function (greetee) {
      console.log(greeting + ", " + greetee + "!");
    }
  }

Dans un langage comme le Javascript, la curryfication est obtenue en retournant les fonctions des fonctions. Ici, la fonction extérieure (greeter) fournit le “greeting” à utiliser tandis que la fonction retour permet à l’appelante de fournir le nom de la destinataire.

Quelle est la finalité du code suivant?

    
1
2
3
4
5
6
(function(){
    var a = b = 42;
  })();
   
  console.log(typeof a);
  console.log(typeof b);

Ce problème est conçu pour sonder les connaissances d’un candidat sur les règles de variable hoisting de Javascript. Les développeurs qui n’ont pas connaissance pas ces règles peuvent présupposer que « undefined » sera enregistré deux fois dans la console, une fois pour a et une fois pour b, ou s’ils ne font pas attention, peuvent penser que « 42 » est enregistré deux fois dans la console, mais ni l’un ni l’autre ne se produit.

Ce code sera transmis à la console:

    
1
2
> undefined
> 42

Pourquoi? La variable a n’est pas définie parce qu’elle est étendue à la fonction anonyme et sort du champ d’application lorsque la fonction est terminée. b d’autre part est dans le champ d’application global. C’est parce qu’il n’est pas déclaré à l’intérieur de la fonction ; le mot-clé var ne s’applique qu’à a ici, donc b est hissé à la portée globale.
C’est une autre bonne raison d’activer le mode strict ; le code ci-dessus serait une erreur dans ce cas. Même en mode strict, les mots-clés let ou const doivent être préférés à var dans la plupart des cas.

Conclusion

Nous espérons que ces questions d’entretien d’embauche pourront vous être utiles. Ces questions représentent ce qui, selon nous, vous donnera la possibilité de sonder les connaissances générales d’un candidat à propos du langage de programmation Javascript et de ses frameworks (tels que Ember.js, React et Angular 2). Mais vous ne devriez pas vous cantonner à ces seules questions, le moyen le plus efficace de recruter un développeur JavaScript compétent reste de faire passer à vos candidats des tests de programmation en ligne.