Questions d'entretien en 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.

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.

N'oubliez pas que l'une des meilleures façons d'évaluer les compétences du candidat pour votre projet est de procéder à une évaluation pratique de la programmation.

Consultez la question JavaScript ci-dessous. Il existe plusieurs façons de la résoudre, de sorte que les choix du candidat peuvent révéler sa façon de coder. Comment la résoudriez-vous ? Cliquez sur l'onglet "Instructions" et essayez :

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 :

> 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.

Pour résumer, nul est la valeur qui signifie "pas de valeur" et indéfini 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, mais une réponse incorrecte de la part d'un candidat doit être considérée comme un drapeau rouge. La réponse est que == effectue toutes les conversions de type nécessaires avant de faire la comparaison alors que === ne le fait pas. Le tableau d'égalité en Javascript est assez tristement célèbre et il est considéré comme une bonne pratique de toujours utiliser === lors de la comparaison des valeurs pour l'égalité.

Que fait la directive use strict ?

L'objectif de la directive "utilisation stricte" est de garantir que le code est exécuté en mode strict. Qu'est-ce que le "mode strict" ? Le mode strict aide les développeurs à éviter une poignée de pièges en Javascript. Activer le mode strict signifie que les éléments suivants entraîneront une erreur.

  1. 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.
  2. Supprimer une variable, c’est-à-dire delete x;
  3. Ecrire dans une propriété en lecture seule (les propriétés en lecture seule sont celles définies via Object.defineProperty()).
  4. Utiliser des variables avec des noms comme arguments ou eval.
  5. Utilisation de l’instruction with

Une réponse à cette question doit inclure un ou plusieurs des éléments ci-dessus. Des points bonus peuvent être accordés si le candidat dit quelque chose sur la portée du mode strict. En particulier, si l'utilisation du mode strict ; est au niveau supérieur, alors il s'applique à l'ensemble du module mais s'il se trouve à l'intérieur d'une définition de fonction, il ne s'applique qu'au code dans le corps de la fonction.

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.

function doIt(onSuccess, onFailure) {
    var err = ...
    if (err) {
      onFailure(err)
    } else {
      onSuccess()
    }
  } 

Toute mention de l'utilisation de l'utilisation de Promise's (voir ci-dessous) au lieu des rappels doit être considérée comme un bon signe.

Qu'est-ce qu'une fonction pure et pourquoi devriez-vous vous en soucier ?

Une fonction pure est une fonction qui renvoie une valeur qui ne dépend que de ses arguments d'entrée et qui, de plus, n'a pas d'effets secondaires. Ce sont des fonctions au sens mathématique du terme. Les fonctions pures sont la pierre angulaire de la programmation fonctionnelle qui est devenue de plus en plus populaire au fil des ans. La programmation 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ébogage. Elle simplifie également le remaniement puisque les fonctions pures peuvent être déplacées (ou supprimées) sans affecter le comportement du programme. Elles donnent lieu à ce que l'on appelle la "transparence référentielle" et des points bonus peuvent être attribués si un candidat en fait mention.

Questions de fond

Qu'est-ce qu'une fermeture ?

Selon Wikipedia :

...une fermeture est un enregistrement stockant une fonction ainsi qu'un environnement.

Une fermeture peut être délicate à définir sur place, mais toute mention de "fonction" avec "environnement" ou "portée" peut être considérée comme étant sur la bonne voie. Une clôture donne à une fonction l'accès à des variables qui lui seraient inaccessibles sans la clôture.

Il est devenu courant d'envelopper le contenu d'un fichier Javascript dans une fonction. Pourquoi le faire ?

Cette technique a été utilisée pour créer une sorte d'espace de noms pour un module, de sorte que les variables définies dans le module sont privées et n'entreront donc pas en conflit avec les variables de l'espace de noms global ou d'autres modules.

Voici un exemple :

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 la mise en œuvre ci-dessus d'une structure de données de file d'attente (une autre bonne question d'entretien en personne), les éléments variables sont privés à la mise en œuvre et ne sont pas visibles ou modifiables par les clients d'une file d'attente.

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

Cette technique peut être généralement utile pour masquer les données et les espaces de noms, mais les regroupements modernes en Javascript comme le webpack sont devenus populaires pour obtenir le même effet.

À quoi servent les promesses ?

Les promesses sont utilisées pour la programmation asynchrone. Elles permettent d'écrire des programmes Javascript de manière non bloquante, tout comme le font les rappels normaux, mais sans le surcroît de travail mental que cette technique implique. Les gens font souvent référence à l'enfer des callbacks qui se produit souvent lorsqu'on utilise des callbacks pour réaliser une asynchronie. Des promesses ont été créées pour atténuer ce problème.

Donnez un exemple de combinaison de plusieurs promesses.

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

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

Enfin, il y a Promise.race() qui est similaire à Promise.all() en ce qu'il crée une Promesse parmi plusieurs autres mais qui diffère en ce que la nouvelle Promesse est résolue lorsqu'une seule des Promesses données est résolue, en particulier, celle qui est résolue en premier.

À quoi servent les mots-clés "async / await" ?

Les promesses ont permis d'atténuer les problèmes liés à une solution de rappel strict de la programmation asynchrone, mais elles peuvent être difficiles à utiliser lorsque de nombreuses promesses sont en jeu et doivent être séquencées avec then() ainsi que devoir gérer les erreurs associées à tout cet enchaînement.

Les mots clés async / await sont utilisés pour permettre aux développeurs d'écrire un code qui ressemble beaucoup au code impératif à l'ancienne - ou au code séquentiel - mais qui est toujours asynchrone.

Les candidats doivent savoir quand il est approprié et possible d'utiliser ces mots clés. En particulier, ils doivent savoir que l'asynchronisation ne peut être placée avant le mot-clé de fonction sur une définition de fonction. La fonction est alors considérée comme un retour de promesse. Le mot-clé "await" ne peut être utilisé qu'à l'intérieur d'une telle fonction asynchrone et doit être placé avant un appel à une fonction qui rendra une Promesse.

Questions sur le tableau blanc

Ecrire un code pour convertir un tableau de chaînes de caractères en un tableau de la longueur de ces chaînes.

Cet exercice devrait être relativement facile à réaliser pour une personne familière avec Javascript. Une solution idéale à ce problème devrait ressembler à ceci :

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

Une autre version pourrait utiliser forEach :

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

Enfin, l'utilisation d'une boucle à l'ancienne fera l'affaire :

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

Les deux dernières doivent être considérées comme un peu comme un drapeau rouge car elles montrent que le candidat n'est pas très familier avec la plus courante des fonctions d'ordre supérieur : la carte.

Ecrivez un code pour additionner un tableau de nombres.

Un bon candidat devrait être capable de proposer une solution du type de celle qui suit :

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 un pour ou un pourChaque mais ces solutions doivent également être considérées comme inférieures et comme un drapeau rouge.

Cette question permet de poser quelques bonnes questions de suivi.

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

Le candidat doit pouvoir expliquer que la fonction d'été (également appelée fonction de réduction) est appelée pour chaque élément du tableau et que la première valeur qui lui est transmise 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. Une certaine indulgence peut être accordée aux candidats qui peuvent obtenir l'ordre inverse de ces deux arguments puisque l'on sait que les différentes langues utilisent l'ordre inverse et que c'est le genre d'information que l'on trouve facilement avec une recherche Google.

Si le candidat donne une réponse similaire à celle ci-dessus, une autre bonne question de suivi est alors posée :

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

Un candidat compétent saura que c'est précisément ce que vise le deuxième argument de réduction : la valeur initiale de l'accumulateur utilisé lors de la réduction du premier élément du réseau. Si le candidat n'a pas déjà répondu à cette prochaine question, posez-lui.

Le deuxième argument pour réduire est facultatif. Que se passe-t-il si vous ne le fournissez pas ?

Dans ce cas, le premier élément du tableau est utilisé comme valeur initiale et la réduction se poursuit avec le deuxième élément du tableau. Cette réponse vous prépare à cette dernière question de suivi :

Que se passe-t-il dans le cas où la réduction est appelée sur un tableau vide sans fournir la valeur initiale facultative de l'accumulateur ?

Cette situation est à l'origine d'une erreur. Le candidat peut s'étendre sur ce point et dire qu'une valeur initiale doit toujours être fournie ou que le code doit veiller à ce que le tableau ne soit pas vide.

Écrivez une fonction qui peut être appelée comme suit : greeter("Hello")("Candidate") et enregistrera "Hello, Candidate !" sur la console

Les candidats avisés reconnaîtront cette technique comme un curry où les fonctions sont capables d'accepter moins que le nombre total d'arguments. L'appel d'une telle fonction est connu sous le nom d'application partielle. Toute mention de ce type est un bon signe. Ils peuvent également faire remarquer qu'il s'agit d'une fonction d'ordre supérieur (HOF) puisqu'elle est implémentée comme une fonction renvoyant une autre fonction.

Une mise en œuvre pourrait ressembler à ceci :

function greeter(greeting) {
    return function (greetee) {
      console.log(greeting + ", " + greetee + "!");
    }
  } 

Dans un langage comme le Javascript, le curry est réalisé en retournant des fonctions à partir de fonctions. Ici, la fonction extérieure (greeter) fournit la salutation à utiliser tandis que la fonction retournée permet à l'appelant de fournir le nom du salué.

Quel est le résultat du code suivant ?

(function(){
    var a = b = 42;
  })();
   
  console.log(typeof a);
  console.log(typeof b);
 

Ce problème est destiné à sonder les connaissances d'un candidat sur les règles de levage de variables de Javascript. Les développeurs qui ne connaissent pas ces règles peuvent supposer que "indéfini" sera enregistré deux fois sur la console, une fois pour a et une fois pour b, ou s'ils ne font pas attention, ils peuvent penser que "42" est enregistré deux fois sur la console, mais ce n'est pas le cas.

Le code ci-dessus sera affiché sur la console

> undefined
> 42 

Pourquoi ? La variable a n'est pas définie parce qu'elle se rapporte à la fonction anonyme et sort du champ d'application lorsque la fonction est terminée. b, en revanche, se trouve dans le champ d'application global. Cela est dû au fait qu'elle n'est pas déclarée à l'intérieur de la fonction ; le mot-clé var ne s'applique qu'à a ici, donc b est placé dans le champ d'application global.

C'est une autre bonne raison d'activer le mode strict ; le code ci-dessus serait une erreur dans ce cas. Même en utilisant le mode strict, les mots-clés let ou const doivent être préférés à var dans la plupart des cas.

Conclusion

La capacité de votre entreprise à rester compétitive et à mettre vos produits sur le marché avant la concurrence dépend de nombreux facteurs, mais l'un des plus importants est la qualité des développeurs que vous avez pour créer ces produits. Ces questions d'entretien en Javascript vous aideront à trouver les meilleurs développeurs dans votre vivier de candidats, mais c'est à vous qu'il appartient en fin de compte de décider de leur niveau de qualité.

Utilisez autant ou aussi peu de questions d'entretien que vous le souhaitez ; vous n'avez pas besoin de les utiliser toutes pour un seul candidat. Ces questions représentent ce qui, selon nous, vous permettra d'approfondir les connaissances générales et approfondies d'un candidat sur Javascript et ses cadres (comme React, Angular 2 et Ember.js). Mais vous ne devez pas vous sentir limité à ces seules questions. N'hésitez pas à poser les vôtres et si vous en trouvez qui vous semblent utiles, faites-le nous savoir ; nous serions ravis de les entendre !