JavaScript

Expliquer la délégation d'événements

La délégation d'événements est une technique qui consiste à ajouter des écouteurs d'événements à un élément parent au lieu de les ajouter aux éléments descendants. L'écouteur se déclenchera chaque fois que l'événement sera déclenché sur les éléments descendants en raison du bullage des événements dans le DOM. Les avantages de cette technique sont les suivants :

  • L'empreinte mémoire diminue car un seul gestionnaire est nécessaire sur l'élément parent, plutôt que de devoir attacher des gestionnaires d'événements à chaque descendant.
  • Il n'est pas nécessaire de dissocier le gestionnaire des éléments qui sont supprimés et de lier l'événement aux nouveaux éléments.

Expliquer comment `this` fonctionne en JavaScript

Il n'y a pas d'explication simple pour this ; c'est l'un des concepts les plus déroutants en JavaScript. Une explication évasive est que la valeur de this dépend de la façon dont la fonction est appelée. J'ai lu de nombreuses explications sur this en ligne, et j'ai trouvé l'explication d'[Arnav Aggrawal] la plus claire. Les règles suivantes sont appliquées :

  1. Si le mot-clé new est utilisé lors de l'appel de la fonction, this à l'intérieur de la fonction est un tout nouvel objet.
  2. Si apply, call ou bind sont utilisés pour appeler/créer une fonction, this à l'intérieur de la fonction est l'objet passé en argument.
  3. Si une fonction est appelée comme une méthode, comme obj.method()this est l'objet dont la fonction est une propriété.
  4. Si une fonction est invoquée comme une invocation de fonction libre, c'est-à-dire qu'elle a été invoquée sans aucune des conditions ci-dessus, this est l'objet global. Dans un navigateur, c'est l'objet window. Si en mode strict ('use strict'), this sera undefined au lieu de l'objet global.
  5. Si plusieurs des règles ci-dessus s'appliquent, la règle la plus élevée l'emporte et définira la valeur this.
  6. Si la fonction est une fonction fléchée ES2015, elle ignore toutes les règles ci-dessus et reçoit la valeur this de sa portée environnante au moment de sa création.

Pour une explication approfondie, consultez son [article sur Medium].

Pouvez-vous donner un exemple de l'une des façons dont le travail avec this a changé en ES6 ?

ES6 vous permet d'utiliser les [fonctions fléchées] qui utilisent la [portée lexicale englobante]. C'est généralement pratique, mais cela empêche l'appelant de contrôler le contexte via .call ou .apply — les conséquences étant qu'une bibliothèque telle que jQuery ne liera pas correctement this dans vos fonctions de gestionnaire d'événements. Il est donc important de garder cela à l'esprit lors de la refactorisation de grandes applications héritées.

Expliquer comment fonctionne l'héritage prototypal

Tous les objets JavaScript ont une propriété __proto__, à l'exception des objets créés avec Object.create(null), qui est une référence à un autre objet, appelé le « prototype » de l'objet. Lorsqu'une propriété est accédée sur un objet et que la propriété n'est pas trouvée sur cet objet, le moteur JavaScript regarde le __proto__ de l'objet, puis le __proto__ du __proto__ et ainsi de suite, jusqu'à ce qu'il trouve la propriété définie sur l'un des __proto__s ou qu'il atteigne la fin de la chaîne de prototypes. Ce comportement simule l'héritage classique, mais il s'agit en réalité davantage d'une [délégation que d'un héritage].

Exemple d'héritage prototypal

// Constructeur d'objet parent. function Animal(name) { this.name = name; } // Ajouter une méthode au prototype de l'objet parent. Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.'); }; // Constructeur d'objet enfant. function Dog(name) { Animal.call(this, name); // Appeler le constructeur parent. } // Définir le prototype de l'objet enfant comme étant le prototype du parent. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Ajouter une méthode au prototype de l'objet enfant. Dog.prototype.bark = function () { console.log('Woof!'); }; // Créer une nouvelle instance de Dog. const bolt = new Dog('Bolt'); // Appeler des méthodes sur l'objet enfant. console.log(bolt.name); // "Bolt" bolt.makeSound(); // "The Dog makes a sound." bolt.bark(); // "Woof!"

Points à noter :

  • .makeSound n'est pas défini sur Dog, donc le moteur remonte la chaîne de prototypes et trouve .makeSound hérité de Animal.
  • L'utilisation de Object.create pour construire la chaîne d'héritage n'est plus recommandée. Utilisez Object.setPrototypeOf à la place.

Que pensez-vous d'AMD vs CommonJS ?

Les deux sont des moyens d'implémenter un système de modules, qui n'était pas nativement présent en JavaScript avant l'arrivée d'ES2015. CommonJS est synchrone tandis qu'AMD (Asynchronous Module Definition) est évidemment asynchrone. CommonJS est conçu pour le développement côté serveur tandis qu'AMD, avec son support du chargement asynchrone des modules, est davantage destiné aux navigateurs.

Je trouve la syntaxe AMD assez verbeuse et CommonJS est plus proche du style dans lequel vous écririez les instructions d'importation dans d'autres langages. La plupart du temps, je trouve AMD inutile, car si vous serviez tout votre JavaScript dans un seul fichier bundle concaténé, vous ne bénéficieriez pas des propriétés de chargement asynchrone. De plus, la syntaxe CommonJS est plus proche du style Node d'écriture de modules et il y a moins de surcharge de commutation de contexte lors du passage du développement JavaScript côté client à côté serveur.

Je suis heureux qu'avec les modules ES2015, qui prennent en charge le chargement synchrone et asynchrone, nous puissions enfin nous en tenir à une seule approche. Bien qu'il n'ait pas été entièrement déployé dans les navigateurs et dans Node, nous pouvons toujours utiliser des transpilateurs pour convertir notre code.

Expliquez pourquoi ce qui suit ne fonctionne pas comme une IIFE : `function foo(){ }();`. Que doit-on modifier pour en faire correctement une IIFE ?

IIFE signifie Immediately Invoked Function Expressions (Expressions de fonctions invoquées immédiatement). L'analyseur JavaScript lit function foo(){ }(); comme function foo(){ } et ();, où le premier est une déclaration de fonction et le second (une paire de parenthèses) est une tentative d'appel de fonction sans nom spécifié, d'où l'erreur Uncaught SyntaxError: Unexpected token ).

Voici deux façons de corriger cela qui impliquent l'ajout de parenthèses supplémentaires : (function foo(){ })() et (function foo(){ }()). Les instructions qui commencent par function sont considérées comme des déclarations de fonction ; en enveloppant cette fonction dans (), elle devient une expression de fonction qui peut ensuite être exécutée avec le () subséquent. Ces fonctions ne sont pas exposées dans la portée globale et vous pouvez même omettre leur nom si vous n'avez pas besoin de vous y référer à l'intérieur du corps.

Vous pouvez également utiliser l'opérateur void : void function foo(){ }();. Malheureusement, cette approche présente un problème. L'évaluation de l'expression donnée est toujours undefined, donc si votre fonction IIFE retourne quelque chose, vous ne pouvez pas l'utiliser. Un exemple :

const foo = void (function bar() { return 'foo'; })(); console.log(foo); // undefined

Quelle est la différence entre une variable qui est : `null`, `undefined` ou non déclarée ? Comment vérifieriez-vous ces états ?

Les variables non déclarées sont créées lorsque vous affectez une valeur à un identifiant qui n'a pas été créé précédemment à l'aide de var, let ou const. Les variables non déclarées seront définies globalement, en dehors de la portée actuelle. En mode strict, une ReferenceError sera levée si vous tentez d'affecter une valeur à une variable non déclarée. Les variables non déclarées sont mauvaises, tout comme les variables globales. Évitez-les à tout prix ! Pour les vérifier, enveloppez leur utilisation dans un bloc try/catch.

function foo() { x = 1; // Lance une ReferenceError en mode strict } foo(); console.log(x); // 1

Une variable qui est undefined est une variable qui a été déclarée, mais à laquelle aucune valeur n'a été attribuée. Elle est de type undefined. Si une fonction ne renvoie aucune valeur suite à son exécution et qu'elle est affectée à une variable, la variable aura également la valeur undefined. Pour la vérifier, comparez en utilisant l'opérateur d'égalité stricte (===) ou typeof qui donnera la chaîne 'undefined'. Notez que vous ne devriez pas utiliser l'opérateur d'égalité abstraite pour vérifier, car il renverra également true si la valeur est null.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Faux, n'utilisez pas ça pour vérifier ! function bar() {} var baz = bar(); console.log(baz); // undefined

Une variable qui est null aura été explicitement affectée à la valeur null. Elle représente aucune valeur et est différente de undefined dans le sens où elle a été explicitement affectée. Pour vérifier null, comparez simplement en utilisant l'opérateur d'égalité stricte. Notez que, comme ci-dessus, vous ne devriez pas utiliser l'opérateur d'égalité abstraite (==) pour vérifier, car il renverra également true si la valeur est undefined.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Faux, n'utilisez pas ça pour vérifier !

Par habitude personnelle, je ne laisse jamais mes variables non déclarées ou non affectées. Je leur affecterai explicitement null après les avoir déclarées si je n'ai pas l'intention de les utiliser encore. Si vous utilisez un linter dans votre flux de travail, il pourra généralement également vérifier que vous ne référencez pas de variables non déclarées.

Qu'est-ce qu'une fermeture (closure), et comment/pourquoi l'utiliseriez-vous ?

Une fermeture est la combinaison d'une fonction et de l'environnement lexical dans lequel cette fonction a été déclarée. Le mot « lexical » fait référence au fait que la portée lexicale utilise l'emplacement où une variable est déclarée dans le code source pour déterminer où cette variable est disponible. Les fermetures sont des fonctions qui ont accès aux variables de la fonction externe (englobante) – chaîne de portée, même après que la fonction externe est revenue.

Pourquoi en utiliser une ?

  • Confidentialité des données / émulation de méthodes privées avec des fermetures. Couramment utilisé dans le [modèle de module].
  • [Applications partielles ou currying].

Pouvez-vous décrire la principale différence entre une boucle `.forEach` et une boucle `.map()` et pourquoi choisiriez-vous l'une plutôt que l'autre ?

Pour comprendre les différences entre les deux, examinons ce que fait chaque fonction.

forEach

  • Itère sur les éléments d'un tableau.
  • Exécute une fonction de rappel pour chaque élément.
  • Ne renvoie aucune valeur.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Faire quelque chose avec num et/ou index. }); // doubled = undefined

map

  • Itère sur les éléments d'un tableau.
  • "Mappe" chaque élément à un nouvel élément en appelant la fonction sur chaque élément, créant un nouveau tableau en conséquence.
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

La principale différence entre .forEach et .map() est que .map() renvoie un nouveau tableau. Si vous avez besoin du résultat, mais que vous ne souhaitez pas modifier le tableau d'origine, .map() est le choix évident. Si vous avez simplement besoin d'itérer sur un tableau, forEach est un bon choix.

Quel est un cas d'utilisation typique pour les fonctions anonymes ?

Elles peuvent être utilisées dans les IIFE pour encapsuler du code dans une portée locale afin que les variables qui y sont déclarées ne fuient pas vers la portée globale.

(function () { // Du code ici. })();

En tant que fonction de rappel utilisée une seule fois et n'ayant pas besoin d'être utilisée ailleurs. Le code semblera plus autonome et lisible lorsque les gestionnaires sont définis directement à l'intérieur du code qui les appelle, plutôt que d'avoir à chercher ailleurs pour trouver le corps de la fonction.

setTimeout(function () { console.log('Bonjour le monde !'); }, 1000);

Arguments pour les constructions de programmation fonctionnelle ou Lodash (similaire aux callbacks).

const arr = [1, 2, 3]; const double = arr.map(function (el) { return el * 2; }); console.log(double); // [2, 4, 6]

Comment organisez-vous votre code ? (modèle de module, héritage classique ?)

Dans le passé, j'ai utilisé Backbone pour mes modèles, ce qui encourage une approche plus orientée objet, en créant des modèles Backbone et en leur attachant des méthodes.

Le modèle de module est toujours excellent, mais de nos jours, j'utilise React/Redux qui utilisent un flux de données unidirectionnel basé sur l'architecture Flux. Je représenterais les modèles de mon application en utilisant des objets simples et j'écrirais des fonctions utilitaires pures pour manipuler ces objets. L'état est manipulé à l'aide d'actions et de réducteurs comme dans toute autre application Redux.

J'évite d'utiliser l'héritage classique chaque fois que possible. Si je le fais, je respecte [ces règles].

Quelle est la différence entre les objets hôtes et les objets natifs ?

Les objets natifs sont des objets qui font partie du langage JavaScript définis par la spécification ECMAScript, tels que String, Math, RegExp, Object, Function, etc.

Les objets hôtes sont fournis par l'environnement d'exécution (navigateur ou Node), tels que window, XMLHTTPRequest, etc.

Différence entre : `function Person(){}`, `var person = Person()`, et `var person = new Person()` ?

Cette question est assez vague. Ma meilleure supposition est qu'elle porte sur les constructeurs en JavaScript. Techniquement parlant, function Person(){} est juste une déclaration de fonction normale. La convention est d'utiliser PascalCase pour les fonctions qui sont destinées à être utilisées comme constructeurs.

var person = Person() invoque Person comme une fonction, et non comme un constructeur. L'invoquer de cette manière est une erreur courante si la fonction est destinée à être utilisée comme constructeur. Typiquement, le constructeur ne retourne rien, donc l'invocation du constructeur comme une fonction normale retournera undefined et cela sera assigné à la variable destinée à l'instance.

var person = new Person() crée une instance de l'objet Person en utilisant l'opérateur new, qui hérite de Person.prototype. Une alternative serait d'utiliser Object.create, comme par exemple : Object.create(Person.prototype).

function Person(name) { this.name = name; } var person = Person('John'); console.log(person); // undefined console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined var person = new Person('John'); console.log(person); // Person { name: "John" } console.log(person.name); // "john"

Quelle est la différence entre `.call` et `.apply` ?

Les deux .call et .apply sont utilisés pour invoquer des fonctions et le premier paramètre sera utilisé comme valeur de this dans la fonction. Cependant, .call prend des arguments séparés par des virgules comme arguments suivants tandis que .apply prend un tableau d'arguments comme argument suivant. Un moyen facile de s'en souvenir est C pour call et les arguments séparés par des virgules et A pour apply et un tableau d'arguments.

function add(a, b) { return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

Expliquer `Function.prototype.bind`.

Mot pour mot de [MDN] :

La méthode bind() crée une nouvelle fonction qui, lorsqu'elle est appelée, a son mot-clé this défini à la valeur fournie, avec une séquence donnée d'arguments précédant tout argument fourni lorsque la nouvelle fonction est appelée.

Selon mon expérience, elle est surtout utile pour lier la valeur de this dans les méthodes de classes que vous souhaitez passer à d'autres fonctions. C'est fréquemment fait dans les composants React.

Quand utiliseriez-vous `document.write()` ?

document.write() écrit une chaîne de texte dans un flux de document ouvert par document.open(). Lorsque document.write() est exécuté après le chargement de la page, il appellera document.open qui efface tout le document ( <head> et <body> supprimés !) et remplace le contenu par la valeur du paramètre donné. Par conséquent, il est généralement considéré comme dangereux et sujet à des abus.

Il existe des réponses en ligne qui expliquent que document.write() est utilisé dans le code d'analyse ou [lorsque vous souhaitez inclure des styles qui ne devraient fonctionner que si JavaScript est activé]. Il est même utilisé dans HTML5 boilerplate pour [charger des scripts en parallèle et préserver l'ordre d'exécution] ! Cependant, je soupçonne que ces raisons pourraient être obsolètes et qu'à l'heure actuelle, elles peuvent être réalisées sans utiliser document.write(). Veuillez me corriger si je me trompe à ce sujet.

Quelle est la différence entre la détection de fonctionnalités, l'inférence de fonctionnalités et l'utilisation de la chaîne UA ?

Détection de fonctionnalités

La détection de fonctionnalités consiste à déterminer si un navigateur prend en charge un certain bloc de code, et à exécuter un code différent selon qu'il le prend en charge (ou non), afin que le navigateur puisse toujours offrir une expérience fonctionnelle plutôt que de planter/erreur dans certains navigateurs. Par exemple :

if ('geolocation' in navigator) { // Peut utiliser navigator.geolocation } else { // Gérer l'absence de fonctionnalité }

[Modernizr] est une excellente bibliothèque pour gérer la détection de fonctionnalités.

Inférence de fonctionnalités

L'inférence de fonctionnalités vérifie une fonctionnalité comme la détection de fonctionnalités, mais utilise une autre fonction car elle suppose qu'elle existera également, par exemple :

if (document.getElementsByTagName) { element = document.getElementById(id); }

Ce n'est pas vraiment recommandé. La détection de fonctionnalités est plus fiable.

Chaîne UA

Il s'agit d'une chaîne signalée par le navigateur qui permet aux pairs du protocole réseau d'identifier le type d'application, le système d'exploitation, le fournisseur de logiciels ou la version du logiciel de l'agent utilisateur du logiciel demandeur. Elle peut être consultée via navigator.userAgent. Cependant, la chaîne est difficile à analyser et peut être falsifiée. Par exemple, Chrome se présente à la fois comme Chrome et Safari. Donc, pour détecter Safari, vous devez vérifier la chaîne Safari et l'absence de la chaîne Chrome. Évitez cette méthode.

Expliquer Ajax avec le plus de détails possible.

Ajax (asynchronous JavaScript and XML) est un ensemble de techniques de développement web utilisant de nombreuses technologies web côté client pour créer des applications web asynchrones. Avec Ajax, les applications web peuvent envoyer et récupérer des données d'un serveur de manière asynchrone (en arrière-plan) sans interférer avec l'affichage et le comportement de la page existante. En découplant la couche d'échange de données de la couche de présentation, Ajax permet aux pages web, et par extension aux applications web, de modifier le contenu dynamiquement sans avoir besoin de recharger la page entière. En pratique, les implémentations modernes utilisent couramment JSON au lieu de XML, en raison des avantages de JSON étant natif de JavaScript.

L'API XMLHttpRequest est fréquemment utilisée pour la communication asynchrone ou, de nos jours, l'API fetch().

Quels sont les avantages et les inconvénients de l'utilisation d'Ajax ?

Avantages

  • Meilleure interactivité. Le nouveau contenu du serveur peut être modifié dynamiquement sans avoir besoin de recharger toute la page.
  • Réduction des connexions au serveur car les scripts et les feuilles de style ne doivent être demandés qu'une seule fois.
  • L'état peut être maintenu sur une page. Les variables JavaScript et l'état du DOM persisteront car la page du conteneur principal n'a pas été rechargée.
  • Essentiellement la plupart des avantages d'une SPA.

Inconvénients

  • Les pages web dynamiques sont plus difficiles à mettre en signet.
  • Ne fonctionne pas si JavaScript a été désactivé dans le navigateur.
  • Certains robots d'exploration web n'exécutent pas JavaScript et ne verraient pas le contenu chargé par JavaScript.
  • Les pages web utilisant Ajax pour récupérer des données devront probablement combiner les données distantes récupérées avec des modèles côté client pour mettre à jour le DOM. Pour que cela se produise, JavaScript devra être analysé et exécuté sur le navigateur, et les appareils mobiles bas de gamme pourraient avoir du mal avec cela.
  • Essentiellement la plupart des inconvénients d'une SPA.

Expliquer comment JSONP fonctionne (et comment ce n'est pas vraiment de l'Ajax).

JSONP (JSON with Padding) est une méthode couramment utilisée pour contourner les politiques de même origine dans les navigateurs web, car les requêtes Ajax de la page actuelle vers un domaine d'origine croisée ne sont pas autorisées.

JSONP fonctionne en effectuant une requête vers un domaine d'origine croisée via une balise <script> et généralement avec un paramètre de requête callback, par exemple : https://example.com?callback=printData. Le serveur enveloppera alors les données dans une fonction appelée printData et les renverra au client.

<script> function printData(data) { console.log(`Mon nom est ${data.name} !`); } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

Le client doit avoir la fonction printData dans sa portée globale et la fonction sera exécutée par le client lorsque la réponse du domaine d'origine croisée est reçue.

JSONP peut être dangereux et a des implications en matière de sécurité. Comme JSONP est réellement du JavaScript, il peut faire tout ce que JavaScript peut faire, vous devez donc faire confiance au fournisseur des données JSONP.

De nos jours, [CORS] est l'approche recommandée et JSONP est considéré comme un hack.

Avez-vous déjà utilisé du templating JavaScript ? Si oui, quelles bibliothèques avez-vous utilisées ?

Oui. Handlebars, Underscore, Lodash, AngularJS et JSX. Je n'aimais pas le templating dans AngularJS parce qu'il utilisait beaucoup de chaînes dans les directives et les fautes de frappe n'étaient pas détectées. JSX est mon nouveau favori car il est plus proche de JavaScript et il n'y a presque pas de syntaxe à apprendre. De nos jours, vous pouvez même utiliser les littéraux de chaîne de gabarit ES2015 comme un moyen rapide de créer des modèles sans dépendre de code tiers.

const template = `<div>Mon nom est : ${name}</div>`;

Cependant, soyez conscient d'une XSS potentielle dans l'approche ci-dessus car les contenus ne sont pas échappés pour vous, contrairement aux bibliothèques de templating.

Expliquer le "hoisting".

Le hoisting est un terme utilisé pour expliquer le comportement des déclarations de variables dans votre code. Les variables déclarées ou initialisées avec le mot-clé var verront leur déclaration "déplacée" vers le haut de leur portée de module/fonction, ce que nous appelons le hoisting. Cependant, seule la déclaration est hissée, l'affectation (s'il y en a une) restera là où elle est.

Notez que la déclaration n'est pas réellement déplacée - le moteur JavaScript analyse les déclarations pendant la compilation et prend conscience des déclarations et de leurs portées. Il est juste plus facile de comprendre ce comportement en visualisant les déclarations comme étant hissées au sommet de leur portée. Expliquons avec quelques exemples.

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

Les déclarations de fonctions ont le corps hissé tandis que les expressions de fonctions (écrites sous forme de déclarations de variables) n'ont que la déclaration de variable hissée.

// Déclaration de fonction console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Function: foo] // Expression de fonction console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Function: bar]

Les variables déclarées via let et const sont également hissées. Cependant, contrairement à var et function, elles ne sont pas initialisées et y accéder avant la déclaration entraînera une exception ReferenceError. La variable se trouve dans une "zone morte temporelle" (temporal dead zone) du début du bloc jusqu'à ce que la déclaration soit traitée.

Décrivez le bullage des événements.

Lorsqu'un événement se déclenche sur un élément du DOM, il tente de gérer l'événement s'il y a un écouteur attaché, puis l'événement remonte vers son parent et la même chose se produit. Ce bullage se produit le long des ancêtres de l'élément jusqu'au document. Le bullage des événements est le mécanisme derrière la délégation d'événements.

Quelle est la différence entre un "attribut" et une "propriété" ?

Les attributs sont définis dans le balisage HTML, mais les propriétés sont définies dans le DOM. Pour illustrer la différence, imaginez que nous ayons ce champ de texte dans notre HTML : <input type="text" value="Hello">.

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello

Mais après avoir modifié la valeur du champ de texte en y ajoutant "World!", cela devient :

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

Pourquoi étendre les objets JavaScript intégrés n'est-il pas une bonne idée ?

Étendre un objet JavaScript intégré/natif signifie ajouter des propriétés/fonctions à son prototype. Bien que cela puisse sembler une bonne idée au début, c'est dangereux dans la pratique. Imaginez que votre code utilise quelques bibliothèques qui étendent toutes deux Array.prototype en ajoutant la même méthode contains, les implémentations se chevaucheront et votre code se brisera si le comportement de ces deux méthodes n'est pas le même.

La seule fois où vous pourriez vouloir étendre un objet natif est lorsque vous souhaitez créer un polyfill, fournissant essentiellement votre propre implémentation pour une méthode qui fait partie de la spécification JavaScript mais qui pourrait ne pas exister dans le navigateur de l'utilisateur en raison de son ancienneté.

Différence entre l'événement `load` du document et l'événement `DOMContentLoaded` du document ?

L'événement DOMContentLoaded est déclenché lorsque le document HTML initial a été complètement chargé et analysé, sans attendre que les feuilles de style, les images et les sous-cadres aient fini de charger.

L'événement load de window n'est déclenché qu'après le chargement du DOM et de toutes les ressources et actifs dépendants.

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

== est l'opérateur d'égalité abstraite tandis que === est l'opérateur d'égalité stricte. L'opérateur == comparera l'égalité après avoir effectué les conversions de type nécessaires. L'opérateur === ne fera pas de conversion de type, donc si deux valeurs ne sont pas du même type, === retournera simplement false. Lors de l'utilisation de ==, des choses étranges peuvent se produire, telles que :

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

Mon conseil est de ne jamais utiliser l'opérateur ==, sauf par commodité lors de la comparaison avec null ou undefined, où a == null retournera true si a est null ou undefined.

var a = null; console.log(a == null); // true console.log(a == undefined); // true

Expliquez la politique de même origine (same-origin policy) en ce qui concerne JavaScript.

La politique de même origine empêche JavaScript d'effectuer des requêtes entre différents domaines. Une origine est définie comme une combinaison de schéma URI, de nom d'hôte et de numéro de port. Cette politique empêche un script malveillant sur une page d'obtenir l'accès à des données sensibles sur une autre page web via le modèle d'objet de document de cette page.

Faites fonctionner ceci :

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) { return arr.concat(arr); } duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Ou avec ES6 :

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Pourquoi appelle-t-on cela une expression ternaire, que signifie le mot "Ternary" ?

"Ternary" indique trois, et une expression ternaire accepte trois opérandes : la condition de test, l'expression « then » et l'expression « else ». Les expressions ternaires ne sont pas spécifiques à JavaScript et je ne sais pas pourquoi elles figurent même dans cette liste.

Qu'est-ce que `"use strict";` ? Quels sont les avantages et les inconvénients de son utilisation ?

'use strict' est une directive utilisée pour activer le mode strict dans l'ensemble d'un script ou dans des fonctions individuelles. Le mode strict est une manière d'opter pour une variante restreinte de JavaScript.

Avantages :

  • Rend impossible la création accidentelle de variables globales.
  • Fait échouer explicitement par une exception des affectations qui échoueraient silencieusement autrement.
  • Fait lancer une exception lors de tentatives de suppression de propriétés indélébiles (alors qu'auparavant la tentative n'aurait simplement aucun effet).
  • Exige que les noms de paramètres de fonction soient uniques.
  • this est undefined dans le contexte global.
  • Intercepte certaines erreurs courantes de codage, en lançant des exceptions.
  • Désactive des fonctionnalités confuses ou mal conçues.

Inconvénients :

  • De nombreuses fonctionnalités manquantes auxquelles certains développeurs sont habitués.
  • Plus d'accès à function.caller et function.arguments.
  • La concaténation de scripts écrits dans différents modes strict peut poser des problèmes.

Globalement, je pense que les avantages l'emportent sur les inconvénients, et je n'ai jamais eu besoin de m'appuyer sur les fonctionnalités bloquées par le mode strict. Je recommande d'utiliser le mode strict.

Créez une boucle for qui itère jusqu'à `100` tout en affichant **"fizz"** aux multiples de `3`, **"buzz"** aux multiples de `5` et **"fizzbuzz"** aux multiples de `3` et de `5`.

Découvrez cette version de FizzBuzz par [Paul Irish].

for (let i = 1; i <= 100; i++) { let f = i % 3 == 0, b = i % 5 == 0; console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i); }

Cependant, je ne vous conseillerais pas d'écrire cela lors d'entretiens. Contentez-vous de l'approche longue mais claire. Pour des versions plus originales de FizzBuzz, consultez le lien de référence ci-dessous.

Pourquoi est-il, en général, préférable de laisser l'espace de noms global d'un site web tel quel et de ne jamais y toucher ?

Chaque script a accès à l'espace de noms global et si tout le monde utilise le namespace global pour définir ses variables, des collisions sont probables. Utilisez le pattern module (IIFE) pour encapsuler vos variables dans un namespace local.

Pourquoi utiliseriez-vous quelque chose comme l'événement `load` ? Cet événement a-t-il des inconvénients ? Connaissez-vous des alternatives, et pourquoi les utiliseriez-vous ?

L'événement load se déclenche à la fin du processus de chargement du document. À ce stade, tous les objets du document sont dans le DOM, et toutes les images, scripts, liens et sous-cadres ont terminé leur chargement.

L'événement DOM DOMContentLoaded se déclenche après que le DOM de la page a été construit, sans attendre le chargement des autres ressources. Cela est préférable dans certains cas lorsque vous n'avez pas besoin de la page complète avant d'initialiser.

Expliquez ce qu'est une application monopage (SPA) et comment en rendre une compatible avec le SEO.

Le texte ci-dessous est extrait de l'excellent [Grab Front End Guide], que j'ai, coïncidence, rédigé !

Les développeurs web d'aujourd'hui considèrent leurs produits comme des web apps plutôt que comme des sites web. Bien qu'il n'y ait pas de différence stricte entre ces deux termes, les web apps sont généralement très interactives et dynamiques, permettant à l'utilisateur d'effectuer des actions et de recevoir une réponse. Traditionnellement, le navigateur reçoit du HTML depuis le serveur et le rend. Lorsqu'un utilisateur navigue vers une autre URL, un rafraîchissement complet de la page est nécessaire et le serveur envoie un nouveau HTML. C'est ce qu'on appelle le rendu côté serveur.

Cependant, dans les SPA modernes, on utilise plutôt le rendu côté client. Le navigateur charge la page initiale depuis le serveur, ainsi que les scripts (frameworks, bibliothèques, code de l'app) et feuilles de style nécessaires pour l'ensemble de l'app. Lorsque l'utilisateur navigue vers d'autres pages, aucun rafraîchissement complet n'est déclenché. L'URL de la page est mise à jour via l’[HTML5 History API]. Les nouvelles données nécessaires, généralement au format JSON, sont récupérées par le navigateur via des requêtes [AJAX] vers le serveur. La SPA met ensuite à jour dynamiquement la page avec ces données grâce au JavaScript déjà téléchargé. Ce modèle est similaire à celui des applications mobiles natives.

Les avantages :

  • L'app est plus réactive et l'utilisateur ne voit pas de flash lors des navigations entre pages.
  • Moins de requêtes HTTP vers le serveur, car les mêmes actifs ne sont pas re-téléchargés à chaque chargement de page.
  • Séparation claire des responsabilités entre client et serveur ; vous pouvez construire de nouveaux clients pour différentes plateformes (mobile, chatbots, montres connectées) sans modifier le code serveur. Vous pouvez aussi faire évoluer indépendamment les stacks technologies client et serveur, tant que le contrat API reste inchangé.

Les inconvénients :

  • Chargement initial plus lourd en raison du téléchargement du framework, du code de l'app et des assets pour plusieurs pages.
  • Nécessité de configurer le serveur pour router toutes les requêtes vers un point d'entrée unique et laisser le routage client prendre le relais.
  • Les SPA dépendent du JavaScript pour rendre le contenu, mais tous les moteurs de recherche n'exécutent pas JavaScript lors du crawling, et peuvent voir une page vide. Cela nuit au SEO. Pour y remédier, vous pouvez soit réaliser un rendu côté serveur, soit utiliser des services comme [Prerender] pour « rendre votre JavaScript dans un navigateur, sauvegarder le HTML statique et le renvoyer aux crawlers ».

Quelle est l'étendue de votre expérience avec les Promises et/ou leurs polyfills ?

Je possède une connaissance pratique des Promises. Une promise est un objet susceptible de produire une seule valeur à un moment donné dans le futur : soit une valeur résolue, soit une raison pour laquelle elle n'est pas résolue (par exemple, une erreur réseau). Une promise peut être dans l'un des trois états possibles : fulfilled, rejected ou pending. Les utilisateurs de Promises peuvent joindre des callbacks pour traiter la valeur résolue ou la raison du rejet.

Parmi les polyfills courants, on trouve $.deferred, Q et Bluebird, mais ils ne respectent pas tous la spécification à 100 %. ES2015 prend en charge les Promises nativement, et les polyfills ne sont généralement plus nécessaires de nos jours.

Quels sont les avantages et les inconvénients d'utiliser des Promises au lieu de callbacks ?

Avantages

  • Évite le callback hell qui peut devenir illisible.
  • Permet d'écrire du code asynchrone séquentiel avec .then(), de manière lisible.
  • Facilite l'exécution asynchrone parallèle avec Promise.all().
  • Avec les callbacks purs, on peut rencontrer les problèmes suivants qui disparaissent avec les Promises :
    • Exécution du callback trop tôt
    • Exécution du callback trop tard (ou jamais)
    • Exécution du callback trop peu ou trop de fois
    • Oubli de transmettre l’environnement/les paramètres nécessaires
    • Masquage d’erreurs/exceptions

Inconvénients

  • Code légèrement plus complexe (discutable).
  • Dans les anciens navigateurs sans prise en charge ES2015, il faut charger un polyfill.

Quels sont certains avantages/inconvénients d'écrire du code JavaScript dans un langage qui se compile en JavaScript ?

Quelques exemples de langages compilant vers JavaScript : CoffeeScript, Elm, ClojureScript, PureScript et TypeScript.

Avantages :

  • Corrige certains problèmes historiques de JavaScript et décourage les anti-patterns.
  • Permet d'écrire du code plus concis grâce à du sucre syntaxique, surtout avant ES2015.
  • Les types statiques (TypeScript) sont formidables pour les gros projets maintenables.

Inconvénients :

  • Nécessité d’un processus de build/compilation, car les navigateurs n’exécutent que du JavaScript.
  • Le débogage peut être pénible si les sourcemaps ne correspondent pas bien au code source.
  • La plupart des développeurs ne connaissent pas ces langages ; il y a une courbe d’apprentissage pour l’équipe.
  • Communautés plus petites (selon le langage), donc moins de ressources, bibliothèques et outils.
  • Ces langages sont toujours en retard sur les dernières évolutions de JavaScript.
  • Il faut rester conscient de ce que génère la compilation, car c’est ce qui s’exécute finalement.

Pratiquement, ES2015 a grandement amélioré JavaScript et le rend agréable à écrire. Je ne vois plus vraiment l’utilité de CoffeeScript aujourd’hui.

Quels outils et techniques utilisez-vous pour déboguer du code JavaScript ?

  • React et Redux :
    • [React Devtools]
    • [Redux Devtools]
  • Vue :
    • [Vue Devtools]
  • JavaScript :
    • [Chrome Devtools]
    • l’instruction debugger
    • le bon vieux console.log pour déboguer

Quelles constructions de langage utilisez-vous pour itérer sur les propriétés d'un objet et les éléments d'un tableau ?

Pour les objets :

  • for-in : for (var property in obj) { console.log(property); }. Cela itère aussi sur les propriétés héritées, pensez donc à vérifier obj.hasOwnProperty(property) avant de l’utiliser.
  • Object.keys() : Object.keys(obj).forEach(function (property) { ... }). Renvoie un tableau des propriétés énumérables.
  • Object.getOwnPropertyNames() : Object.getOwnPropertyNames(obj).forEach(function (property) { ... }). Renvoie un tableau des propriétés énumérables et non énumérables.

Pour les tableaux :

  • Boucle for : for (var i = 0; i < arr.length; i++). Attention au scope de var. Avec ES2015, préférez : for (let i = 0; i < arr.length; i++).
  • forEach : arr.forEach(function (el, index) { ... }). Pratique quand seul l’élément compte. Il existe aussi every et some pour interrompre l’itération.
  • Boucle for-of : for (let elem of arr) { ... }. ES6 combine les avantages des boucles for et forEach : on peut y faire break et c’est plus concis.

Si vous avez besoin à la fois de l’indice et de la valeur, utilisez arr.entries() et la déstructuration :

const arr = ['a', 'b', 'c']; for (let [index, elem] of arr.entries()) { console.log(index, ': ', elem); }

Expliquez la différence entre objets mutables et immuables.

L'immutabilité est un principe central en programmation fonctionnelle, et elle apporte beaucoup aux programmes orientés objets. Un objet mutable est un objet dont l’état peut être modifié après sa création. Un objet immuable est un objet dont l’état ne peut pas être modifié après sa création.

Quel est un exemple d'objet immuable en JavaScript ?

En JavaScript, certains types natifs (nombres, chaînes) sont immuables, mais les objets personnalisés sont généralement mutables.

Quelques manières d’ajouter ou de simuler l’immutabilité sur des objets JavaScript :

Propriétés constantes

En combinant writable: false et configurable: false, vous pouvez créer une propriété constante :

let myObject = {}; Object.defineProperty(myObject, 'number', { value: 42, writable: false, configurable: false, }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42

Empêcher les extensions

Pour empêcher l’ajout de nouvelles propriétés :

var myObject = { a: 2 }; Object.preventExtensions(myObject); myObject.b = 3; console.log(myObject.b); // undefined

Sceller

Object.seal() empêche les extensions et marque toutes les propriétés existantes comme configurable: false.

Geler

Object.freeze() scelle l’objet et rend toutes les propriétés en lecture seule (writable: false).

var immutable = Object.freeze({});

Quels sont les pour et les contre de l'immutabilité ?

Pour :

  • Détection de changement plus simple : on peut comparer par égalité de référence.
  • Moins de complexité mentale, car on n’a pas à se soucier de l’évolution d’un objet.
  • Pas besoin de copies défensives : un objet immuable ne peut pas être modifié.
  • Partage sûr entre threads, car pas de modification concurrente.
  • Les bibliothèques comme ImmutableJS utilisent le partage structurel pour réduire la consommation mémoire.

Contre :

  • Implémentations naïves peuvent être lentes, car chaque modification crée un nouvel objet.
  • Allocation intensive d’objets peut impacter les performances.
  • Structures cycliques (graphes) sont difficiles à modéliser.

Comment obtenir l'immutabilité dans votre propre code ?

Déclarez vos variables avec const et utilisez le spread, Object.assign, Array.concat(), etc., pour créer de nouveaux objets plutôt que de muter l’original.

const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // { race: "human", name: "John" } const alienJohn = { ...john, race: 'alien' }; // { race: "alien", name: "John" }

Expliquez la différence entre fonctions synchrones et asynchrones.

Les fonctions synchrones sont bloquantes : chaque instruction s'achève avant la suivante. Le programme est évalué dans l'ordre exact des instructions, et il est mis en pause si l'une d'entre elles prend du temps.

Les fonctions asynchrones ne bloquent pas : elles acceptent généralement un callback, et l'exécution continue immédiatement après l'appel de la fonction asynchrone. Le callback n'est invoqué que lorsque l'opération asynchrone est terminée et que la pile d'appels est vide. Les opérations lourdes (requêtes réseau, accès base de données) doivent être asynchrones pour ne pas bloquer le thread principal (sinon l'UI se fige dans le navigateur).

Qu'est-ce que la boucle d'événements ? Quelle est la différence entre la pile d'appels et la file d'attente de tâches ?

La boucle d'événements est une boucle mono-thread qui surveille la pile d'appels et vérifie s'il y a des callbacks dans la file d'attente de tâches. Si la pile est vide et qu'il y a des fonctions dans la file, l'une d'elles est déplacée dans la pile pour être exécutée.

Si vous ne l'avez pas déjà fait, regardez la conférence de Philip Roberts sur l'Event Loop, c'est l'une des plus visionnées sur JavaScript.

Expliquez les différences d'utilisation de `foo` entre `function foo() {}` et `var foo = function() {}`

La première est une déclaration de fonction, la seconde est une expression de fonction. La déclaration de fonction est entièrement hoistée, alors que l'expression de fonction suit le même hoisting que les variables. Si vous essayez d'invoquer l'expression avant sa définition, vous obtiendrez une Uncaught TypeError: foo is not a function.

Déclaration de fonction

foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); }

Expression de fonction

foo(); // Uncaught TypeError: foo is not a function var foo = function () { console.log('FOOOOO'); };

Quelles sont les différences entre les variables créées avec `let`, `var` ou `const` ?

Les variables déclarées avec le mot-clé var sont limitées à la fonction dans laquelle elles sont créées, ou, si elles sont créées en dehors de toute fonction, à l'objet global. let et const sont limitées au bloc, ce qui signifie qu'elles ne sont accessibles qu'à l'intérieur de l'ensemble de accolades le plus proche (fonction, bloc if-else ou boucle for).

function foo() { // Toutes les variables sont accessibles à l'intérieur des fonctions. var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; console.log(bar); // bar console.log(baz); // baz console.log(qux); // qux } console.log(bar); // ReferenceError: bar is not defined console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined
if (true) { var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; } // Les variables déclarées avec var sont accessibles partout dans la portée de la fonction. console.log(bar); // bar // Les variables définies avec let et const ne sont pas accessibles en dehors du bloc où elles ont été définies. console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined

var permet aux variables d'être remontées (hoisted), ce qui signifie qu'elles peuvent être référencées dans le code avant d'être déclarées. let et const ne le permettent pas, et lèvent une erreur à la place.

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: impossible d'accéder à la déclaration lexicale 'baz' avant l'initialisation let baz = 'baz'; console.log(bar); // ReferenceError: impossible d'accéder à la déclaration lexicale 'bar' avant l'initialisation const bar = 'bar';

Redéclarer une variable avec var ne lèvera pas d'erreur, mais let et const le feront.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: L'identifiant 'baz' a déjà été déclaré

let et const diffèrent en ce que let permet de réaffecter la valeur de la variable tandis que const ne le permet pas.

// C'est bon. let foo = 'foo'; foo = 'bar'; // Cela provoque une exception. const baz = 'baz'; baz = 'qux';

Quelles sont les différences entre une classe ES6 et les constructeurs de fonctions ES5 ?

Regardons d'abord un exemple de chacun :

// Constructeur de fonction ES5 function Person(name) { this.name = name; } // Classe ES6 class Person { constructor(name) { this.name = name; } }

Pour les constructeurs simples, ils se ressemblent assez.

La principale différence dans le constructeur apparaît lors de l'utilisation de l'héritage. Si nous voulons créer une classe Student qui hérite de Person et ajouter un champ studentId, voici ce que nous devons faire en plus de ce qui précède.

// Constructeur de fonction ES5 function Student(name, studentId) { // Appeler le constructeur de la superclasse pour initialiser les membres dérivés de la superclasse. Person.call(this, name); // Initialiser les propres membres de la sous-classe. this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // Classe ES6 class Student extends Person { constructor(name, studentId) { super(name); this.studentId = studentId; } }

Il est beaucoup plus verbeux d'utiliser l'héritage en ES5 et la version ES6 est plus facile à comprendre et à retenir.

Pouvez-vous proposer un cas d'utilisation pour la nouvelle syntaxe de fonction fléchée `=>` ? En quoi cette nouvelle syntaxe diffère-t-elle des autres fonctions ?

L'un des avantages évidents des fonctions fléchées est de simplifier la syntaxe nécessaire pour créer des fonctions, sans avoir besoin du mot-clé function. Le this à l'intérieur des fonctions fléchées est également lié à la portée englobante, ce qui est différent par rapport aux fonctions régulières où le this est déterminé par l'objet qui l'appelle. Le this à portée lexicale est utile lors de l'invocation de callbacks, en particulier dans les composants React.

Quel est l'avantage d'utiliser la syntaxe fléchée pour une méthode dans un constructeur ?

Le principal avantage d'utiliser une fonction fléchée comme méthode à l'intérieur d'un constructeur est que la valeur de this est définie au moment de la création de la fonction et ne peut pas changer par la suite. Ainsi, lorsque le constructeur est utilisé pour créer un nouvel objet, this fera toujours référence à cet objet. Par exemple, supposons que nous ayons un constructeur Person qui prend un prénom en argument et a deux méthodes pour console.log ce nom, l'une comme fonction régulière et l'autre comme fonction fléchée :

const Person = function (firstName) { this.firstName = firstName; this.sayName1 = function () { console.log(this.firstName); }; this.sayName2 = () => { console.log(this.firstName); }; }; const john = new Person('John'); const dave = new Person('Dave'); john.sayName1(); // John john.sayName2(); // John // La fonction régulière peut voir sa valeur 'this' modifiée, mais la fonction fléchée ne le peut pas john.sayName1.call(dave); // Dave (car "this" est maintenant l'objet dave) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (car 'this' est maintenant l'objet dave) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (car 'this' est maintenant l'objet dave) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (car 'this' est maintenant l'objet window) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

Le principal point à retenir ici est que this peut être modifié pour une fonction normale, mais le contexte reste toujours le même pour une fonction fléchée. Ainsi, même si vous transmettez votre fonction fléchée à différentes parties de votre application, vous n'auriez pas à vous soucier du changement de contexte.

Cela peut être particulièrement utile dans les composants de classe React. Si vous définissez une méthode de classe pour quelque chose comme un gestionnaire de clic en utilisant une fonction normale, et que vous transmettez ensuite ce gestionnaire de clic à un composant enfant en tant que prop, vous devrez également lier this dans le constructeur du composant parent. Si vous utilisez plutôt une fonction fléchée, il n'est pas nécessaire de lier "this", car la méthode obtiendra automatiquement sa valeur "this" de son contexte lexical englobant.

Quelle est la définition d'une fonction d'ordre supérieur ?

Une fonction d'ordre supérieur est toute fonction qui prend une ou plusieurs fonctions comme arguments, qu'elle utilise pour opérer sur certaines données, et/ou renvoie une fonction en résultat. Les fonctions d'ordre supérieur sont destinées à abstraire une opération qui est effectuée de manière répétée. L'exemple classique est map, qui prend un tableau et une fonction comme arguments. map utilise ensuite cette fonction pour transformer chaque élément du tableau, renvoyant un nouveau tableau avec les données transformées. D'autres exemples populaires en JavaScript sont forEach, filter et reduce. Une fonction d'ordre supérieur n'a pas seulement besoin de manipuler des tableaux car il existe de nombreux cas d'utilisation pour renvoyer une fonction à partir d'une autre fonction. Function.prototype.bind en est un exemple en JavaScript.

Map

Supposons que nous ayons un tableau de noms que nous devons transformer en majuscules.

const names = ['irish', 'daisy', 'anna'];

La manière impérative serait la suivante :

const transformNamesToUppercase = function (names) { const results = []; for (let i = 0; i < names.length; i++) { results.push(names[i].toUpperCase()); } return results; }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

L'utilisation de .map(transformerFn) rend le code plus court et plus déclaratif.

const transformNamesToUppercase = function (names) { return names.map((name) => name.toUpperCase()); }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Pouvez-vous donner un exemple de déstructuration d'un objet ou d'un tableau ?

La déstructuration est une expression disponible en ES6 qui permet un moyen succinct et pratique d'extraire les valeurs d'Objets ou de Tableaux et de les placer dans des variables distinctes.

Déstructuration de tableau

// Affectation de variable. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// Échange de variables let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Déstructuration d'objet

// Affectation de variable. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

Les littéraux de gabarit ES6 offrent beaucoup de flexibilité pour générer des chaînes, pouvez-vous donner un exemple ?

Les littéraux de gabarit simplifient l'interpolation de chaînes, ou l'inclusion de variables dans une chaîne. Avant ES2015, il était courant de faire quelque chose comme ceci :

var person = { name: 'Tyler', age: 28 }; console.log( 'Hi, my name is ' + person.name + ' and I am ' + person.age + ' years old!', ); // 'Bonjour, je m'appelle Tyler et j'ai 28 ans !'

Avec les littéraux de gabarit, vous pouvez maintenant créer la même sortie de cette manière :

const person = { name: 'Tyler', age: 28 }; console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`); // 'Bonjour, je m'appelle Tyler et j'ai 28 ans !'

Notez que vous utilisez des apostrophes inverses, pas des guillemets, pour indiquer que vous utilisez un littéral de gabarit et que vous pouvez insérer des expressions à l'intérieur des espaces réservés ${}.

Un deuxième cas d'utilisation utile est la création de chaînes multilignes. Avant ES2015, vous pouviez créer une chaîne multiligne comme ceci :

console.log('This is line one.\nThis is line two.'); // Ceci est la ligne un. // Ceci est la ligne deux.

Ou si vous vouliez la diviser en plusieurs lignes dans votre code afin de ne pas avoir à faire défiler vers la droite dans votre éditeur de texte pour lire une longue chaîne, vous pouviez également l'écrire comme ceci :

console.log('This is line one.\n' + 'This is line two.'); // Ceci est la ligne un. // Ceci est la ligne deux.

Les littéraux de gabarit, cependant, conservent l'espacement que vous leur ajoutez. Par exemple, pour créer la même sortie multiligne que celle que nous avons créée ci-dessus, vous pouvez simplement faire :

console.log(`This is line one. This is line two.`); // Ceci est la ligne un. // Ceci est la ligne deux.

Un autre cas d'utilisation des littéraux de gabarit serait de les utiliser comme substitut aux bibliothèques de templating pour de simples interpolations de variables :

const person = { name: 'Tyler', age: 28 }; document.body.innerHTML = ` <div> <p>Name: ${person.name}</p> <p>Age: ${person.age}</p> </div> `;

Notez que votre code peut être susceptible aux attaques XSS en utilisant .innerHTML. Nettoyez vos données avant de les afficher si elles proviennent d'un utilisateur !

Pouvez-vous donner un exemple de fonction curry et pourquoi cette syntaxe offre un avantage ?

Le currying est un modèle où une fonction avec plus d'un paramètre est divisée en plusieurs fonctions qui, lorsqu'elles sont appelées en série, accumulent tous les paramètres requis un par un. Cette technique peut être utile pour rendre le code écrit dans un style fonctionnel plus facile à lire et à composer. Il est important de noter que pour qu'une fonction soit curried, elle doit commencer comme une seule fonction, puis être divisée en une séquence de fonctions qui acceptent chacune un seul paramètre.

function curry(fn) { if (fn.length === 0) { return fn; } function _curried(depth, args) { return function (newArgument) { if (depth - 1 === 0) { return fn(...args, newArgument); } return _curried(depth - 1, [...args, newArgument]); }; } return _curried(fn.length, []); } function add(a, b) { return a + b; } var curriedAdd = curry(add); var addFive = curriedAdd(5); var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

Quels sont les avantages de l'utilisation de la syntaxe de propagation (spread syntax) et en quoi est-elle différente de la syntaxe de repos (rest syntax) ?

La syntaxe de propagation (spread syntax) d'ES6 est très utile lors du codage dans un paradigme fonctionnel, car nous pouvons facilement créer des copies de tableaux ou d'objets sans recourir à Object.create, slice ou à une fonction de bibliothèque. Cette fonctionnalité du langage est souvent utilisée dans les projets Redux et RxJS.

function putDookieInAnyArray(arr) { return [...arr, 'dookie']; } const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"] const person = { name: 'Todd', age: 29, }; const copyOfTodd = { ...person };

La syntaxe de repos (rest syntax) d'ES6 offre une abréviation pour inclure un nombre arbitraire d'arguments à passer à une fonction. C'est comme l'inverse de la syntaxe de propagation, prenant des données et les plaçant dans un tableau plutôt que de décompresser un tableau de données, et cela fonctionne dans les arguments de fonction, ainsi que dans les affectations de déstructuration de tableaux et d'objets.

function addFiveToABunchOfNumbers(...numbers) { return numbers.map((x) => x + 5); } const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15] const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4] const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4, }; // e: 1, f: 2, others: { g: 3, h: 4 }

Comment pouvez-vous partager du code entre les fichiers ?

Cela dépend de l'environnement JavaScript.

Côté client (environnement de navigateur), tant que les variables/fonctions sont déclarées dans la portée globale (window), tous les scripts peuvent y faire référence. Alternativement, adoptez la définition de module asynchrone (AMD) via RequireJS pour une approche plus modulaire.

Côté serveur (Node.js), la manière courante a été d'utiliser CommonJS. Chaque fichier est traité comme un module et il peut exporter des variables et des fonctions en les attachant à l'objet module.exports.

ES2015 définit une syntaxe de module qui vise à remplacer à la fois AMD et CommonJS. Celle-ci sera finalement prise en charge dans les environnements de navigateur et Node.

Pourquoi voudriez-vous créer des membres de classe statiques ?

Les membres de classe statiques (propriétés/méthodes) ne sont pas liés à une instance spécifique d'une classe et ont la même valeur quelle que soit l'instance qui y fait référence. Les propriétés statiques sont généralement des variables de configuration et les méthodes statiques sont généralement des fonctions utilitaires pures qui ne dépendent pas de l'état de l'instance.