Fonctionnalités JavaScript explicites

Image


Quand je lis un autre article sur les fonctionnalités peu connues du langage JavaScript et que je fais pipi tranquillement des solutions folles dans la console du navigateur, je dis souvent dans ma tête que, bien, ce n’est certainement pas le cas sur la prod!? Après tout, la langue a acquis depuis longtemps une énorme communauté et a une couverture étonnamment large du développement industriel. Si oui, alors pourquoi oublions-nous souvent sa capacité à être compris par tout le monde et propagent-ils littéralement toutes ces constructions spécifiques et «mémorables»? Rendez-le évident!


Raisonnement sur le sujet


Vous pouvez ignorer cette graphomanie.


Si l'on parle de développement industriel, dans la grande majorité des cas, l'exigence de prise en charge d'un code est encore plus importante que la résolution d'une tâche posée par une entreprise. Pour beaucoup, cela est évident, pour certains - en partie (bien sûr, on trouve également des D'Artagnans rares). Plus notre code est clair, moins il y a de risque d'arriver sur l'étagère poussiéreuse, et pour nous et nos successeurs de gagner des problèmes avec le système nerveux.


Ce n'est pas un secret que JavaScript est incroyable dans sa flexibilité, qui est à la fois sa plus grande vertu et une malédiction ennuyeuse. Le chemin du développeur JavaScript est long et extrêmement intéressant: nous absorbons livre par livre, article par article, et acquérons une expérience unique, mais parfois il est vraiment spécifique à la langue. La plus large diffusion de la langue et en même temps un nombre important de non-évidences accumulées et nourries contribuent à la formation de deux fronts: ceux qui idolâtrent presque cette langue, et ceux qui la considèrent comme un canard des droits maladroit et chancelant.


Et tout irait bien, mais souvent les représentants des deux fronts travaillent sur le même projet. Et l'habituel, toute pratique acceptée est un malentendu (réticence à comprendre et même à ignorer) le code de l'autre. Et en fait, "j'ai un développeur Java, et ce n'est pas le vôtre!" . Les adeptes de Javascript eux-mêmes ajoutent de l'huile sur le feu, en disant "personne ne connaît vraiment JavaScript!" oui "je peux l'écrire en une seule ligne sur js!" . J'avoue que j'abuse moi-même une programmation anormale à loisir ...


Vous commencez à ressentir ce problème lorsque vous prenez la place d'un marginal et acquérez une certaine expérience de travail avec les gens et leur code des deux côtés des barricades. La planification et les autres réunions sont plus productives lorsque tous les développeurs se comprennent, non seulement au niveau des secteurs d'activité, mais au moins un peu au niveau de leur mise en œuvre. Le facteur de basse notoire a un impact moindre sur le projet, quand en cas de maladie d'un seul front-end, le reste de l'équipe n'hésite pas à corriger une ligne du fichier .js . Le processus de partage des connaissances au sein de l'équipe et au-delà devient plus transparent pour tout le monde lorsque tout le monde a une image plus détaillée. Et bien dans la même veine.


Je n’invite personne à «faire du plein verre» ou «en forme de T» (comment le dire maintenant?), Mais pourquoi ne levons-nous pas ce rideau un peu même de la communauté JavaScript? Pour ce faire, il suffit d’apporter un peu de clarté à notre code, en utilisant la flexibilité du langage non pas pour montrer, mais pour être compris.


Grandir et prendre ses responsabilités


Pour sa part, JavaScript a depuis longtemps réalisé son rôle, non pas comme un langage pour l'interactivité des pages Internet et «collant» leurs ressources, mais comme un outil puissant et suffisant pour créer des applications multiplateformes complètes et souvent très évolutives.


Conçu à l'origine pour les concepteurs de sites Web, ce «langage de programmation le plus mal compris» marche depuis longtemps, malgré sa popularité et sa pertinence croissantes. Pendant les 13-14 ans précédant l'édition d'ECMAScript 5.1, il est difficile de rappeler des changements importants dans la norme ou de comprendre le vecteur de son développement. À cette époque, sa communauté a grandement contribué à la formation de l'écosystème du langage: Prototype, jQuery, MooTools, etc. Après avoir reçu ces commentaires des développeurs, JavaScript a fait un travail important sur les bugs: la version bruyante de 6 ans d'ES6 en 2015 et maintenant les versions annuelles d'ECMAScript, grâce au processus repensé du comité TC39 pour introduire de nouvelles fonctionnalités dans la spécification.


Eh bien, lorsque nos applications sont devenues suffisamment volumineuses, le prototype de modèle OOP pour décrire les types d'utilisateurs n'était plus justifié en raison d'une approche inhabituelle. Eh bien sérieusement, c'est quoi?


function Animal() { /* Call me via new and I will be the constructor ;) */ } function Rabbit() {} Rabbit.prototype = Object.create(Animal.prototype); Rabbit.prototype.constructor = Rabbit; 

Les classes n'apparaissent pas dans la langue, mais leur syntaxe apparaît. Et le code est devenu accessible aux adeptes du paradigme traditionnel axé sur les classes:


 class Animal { constructor() { /* Obviously, the constructor is here! */ } } class Rabbit extends Animal {} 

Maintenant, au stade du candidat à la libération sont les domaines privés de la classe. Il est difficile de croire que, tôt ou tard, nous arrêterons de nous moquer les uns des autres avec un accord sur la dénomination des propriétés privées par des traits de soulignement.


Dans le même temps, dans un langage où une fonction est un objet de premier ordre et où un événement constant a lieu, c'est assez courant:


 let that = this; setTimeout(function() { that.n += 1; }, 1000); 

Et puis les explications sur ces contextes et la fermeture en JavaScript commencent, ce qui effraie chaque deuxième développeur externe. Mais dans de nombreux cas, le langage évite les surprises inutiles en utilisant explicitement Function.prototype.bind ou même ainsi:


 setTimeout(() => this.n += 1, 1000); 

Nous avons également des fonctions fléchées, et ce sont vraiment des fonctions, pas des interfaces fonctionnelles (oui, Java?). Avec un ensemble étendu de méthodes pour travailler avec un tableau, ils aident également à écrire la ligne de paiement déclarative habituelle des calculs:


 [-1, 2, -3, 4] .filter(x => x > 0) .map(x => Math.pow(2, x)) .reduce((s, x) => s + x, 0); 

La langue se considère à juste titre comme multi-paradigmatique. Mais voici un exemple simple sur la signature d'une fonction:


 function ping(host, count) { count = count || 5; /* send ping to host count times */ } 

Tout d'abord, une personne de passage posera une question disant qu'une fonction ne peut probablement prendre que le premier argument, puis dira ce que l'enfer dans ce cas, le compte devient booléen!? En effet, la fonction a deux utilisations: avec et sans compte. Mais cela n'est pas du tout évident: vous devez regarder la mise en œuvre et comprendre. L'utilisation de JSDoc peut aider, mais ce n'est pas une pratique courante. Et ici, JavaScript est allé de l'avant, ajoutant la prise en charge non pas pour la surcharge, mais au moins pour les paramètres par défaut:


 function ping(host, count = 5) { /* ... */ } 

En résumé, JavaScript a obtenu un grand nombre de choses familières: les générateurs, les itérateurs, les collections Set et les dictionnaires de cartes , les tableaux typés et même les expressions régulières ont commencé à ravir le support de lookbehind ! La langue fait tout pour convenir à beaucoup de choses et devenir conviviale pour tous.


Chemin favorable à l'évidence


Le langage lui-même est certainement bien fait, et il est difficile de contester cela! Mais qu'est-ce qui ne va pas chez nous? Pourquoi rappelons-nous constamment au monde entier que JavaScript est en quelque sorte différent? Examinons des exemples de certaines techniques largement utilisées et posons des questions sur leur pertinence.


Coulée de type


Oui, JavaScript a un système de type dynamique et faible et vous permet d'effectuer des opérations sur n'importe quoi, effectuant implicitement des transformations pour nous. Mais souvent, un casting explicite est encore nécessaire pour nous, et les éléments suivants peuvent être observés:


 let bool = !!(expr); let numb = +(expr); let str = ''+(expr); 

Ces astuces sont connues de tous les développeurs JavaScript et sont motivées par le fait qu'ils disent que vous pouvez "rapidement" transformer quelque chose en quelque chose: par vitesse, on entend ici un bref enregistrement. Peut-il également écrire immédiatement false comme ! 1 ? Si le développeur est tellement préoccupé par les caractères imprimables, alors dans son IDE préféré, vous pouvez facilement configurer le modèle en direct nécessaire ou l'auto-complétion. Et si - pour la taille du code publié, nous le faisons toujours passer par l'obfuscateur, qui sait mieux que le nôtre comment déshumaniser tout cela. Pourquoi pas:


 let bool = Boolean(expr); let numb = Number(expr); let str = String(expr); 

Le résultat est le même, clair pour tout le monde.


Pour les conversions de chaînes, nous devons toString , mais pour les conversions numériques, il existe une valeur intéressante de Of , qui peut également être remplacée. Un exemple classique qui introduit le "non initié" dans une stupeur:


 let timestamp = +new Date; 

Mais Date possède une méthode getTime connue, utilisons-la:


 let timestamp = (new Date()).getTime(); 

ou fonction prête à l'emploi:


 let timestamp = Date.now(); 

Il n'est absolument pas nécessaire d'exploiter la conversion de type implicite.


Opérateurs logiques


Une attention particulière est portée aux opérateurs logiques AND (&&) et OR (||), qui ne sont pas tout à fait logiques en JavaScript: ils acceptent et renvoient des valeurs de tout type. Nous n'entrerons pas dans les détails du fonctionnement du calculateur d' expressions logiques , nous considérerons des exemples. L'option présentée précédemment avec la fonction:


 function ping(host, count) { count = count || 5; /* ... */ } 

Cela pourrait bien ressembler à ceci:


 function ping(host, count) { // OR arguments.length? if (typeof count == 'undefined') { count = 5; } /* ... */ } 

Cette vérification est plus familière et, dans certains cas, peut aider à éviter les erreurs.


Au contraire, cela semble sauvage au développeur qui a initialement choisi le chemin JavaScript. Mais pour la plupart des autres, ce code est vraiment sauvage:


 var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global); 

Oui, il est compact et oui, les bibliothèques populaires peuvent se le permettre. Mais s'il vous plaît, n'en abusons pas, car notre code ne sera pas lu par les contributeurs en JavaScript, mais par les développeurs qui résolvent les problèmes commerciaux dans les délais.


Un tel schéma peut se produire:


 let count = typeof opts == 'object' && opts.count || 5; 

C'est certainement plus court que l'opérateur ternaire habituel, mais lors de la lecture d'un tel code, la première chose dont vous vous souvenez est les priorités des opérations utilisées.


Si nous écrivons une fonction de prédicat, que nous transmettons au même Array.prototype.filter , l' encapsulation de la valeur de retour en booléen est un bon ton. Le but de cette fonction devient immédiatement apparent et il n'y a pas de dissonance parmi les développeurs dont les langages ont les opérateurs logiques "corrects".


Opérations au niveau du bit


Un exemple courant de vérification de la présence d'un élément dans un tableau ou d'une sous-chaîne dans une chaîne à l'aide de NOT (NOT) au niveau du bit, qui est proposé même par certains didacticiels:


 if (~[1, 2, 3].indexOf(1)) { console.log('yes'); } 

Quel problème cela résout-il? nous n'avons pas à vérifier ! == -1 , puisque indexOf obtiendra l'index de l'élément ou -1, et le tilde ajoutera 1 et changera de signe. Ainsi, l'expression se transformera en "faux" dans le cas de l'index -1.


Mais la duplication de code peut être évitée d'une autre manière: placer une vérification dans une fonction distincte de certains objets utils, comme tout le monde, que d'utiliser des opérations au niveau du bit à d'autres fins. Il y a une fonction comprend dans lodash pour cela, et cela ne fonctionne pas via enculer tilde. Vous pouvez vous en réjouir, car dans ECMAScript 2016, la méthode Array.prototype.includes a été corrigée (les lignes en ont également une).


Mais ça y était! Un autre tilde (avec XOR) est utilisé pour arrondir les nombres, en éliminant la partie décimale:


 console.log(~~3.14); // 3 console.log(2.72^0); // 2 

Mais il y a parseInt ou Math.floor à ces fins. Les opérations au niveau du bit ici sont pratiques pour taper rapidement du code dans la console, car elles ont également une faible priorité sur le reste de l'arithmétique. Mais lors d'une révision de code, il vaut mieux ne pas le manquer.


Constructions de syntaxe et de langage


Certaines pratiques étranges sont difficiles à attribuer à une section particulière. Par exemple, ils disent que les crochets sont facultatifs lors de l'appel du constructeur et que les deux expressions suivantes sont identiques:


 let rabbit = new Rabbit(); let rabbit = new Rabbit; 

Et ça l'est vraiment! mais pourquoi créer une question à partir de zéro? Toutes les langues ne peuvent pas se vanter d'une telle «fonctionnalité». Et si vous le souhaitez toujours, que ce soit un accord sur l'ensemble du projet. Sinon, il y a un faux sentiment qu'il y a une différence.


Une situation similaire avec la déclaration d'un ensemble de variables. La syntaxe des directives var et let vous permet de déclarer (et de définir) plusieurs variables à la fois, séparées par des virgules:


 let count = 5, host, retry = true; 

Quelqu'un utilise des sauts de ligne pour plus de lisibilité, mais dans tous les cas, cette syntaxe n'est pas courante dans les langues populaires. Personne ne donnera un coup de main et vous demandera si vous écrivez comme ceci:


 let count = 5; let retry = true; let host; 

Encore une fois, s'il existe un accord sur le bon style au niveau du projet / de l'entreprise, il n'y a pas de questions. C'est juste que vous n'avez pas à combiner trop de syntaxe pour votre humeur.


Il existe des constructions spécifiques dans le langage, comme l'IIFE, qui vous permet d'appeler une fonction immédiatement à l'endroit de sa définition. L'astuce consiste pour l'analyseur à reconnaître une expression fonctionnelle, pas une déclaration de fonction. Et cela peut être fait de différentes manières: encapsulation classique entre crochets, via void ou tout autre opérateur unaire. Et il n'y a rien de merveilleux là-dedans! Il faut choisir la seule option et ne pas la laisser sans avoir besoin:


 (function() { /* ... */ }()); 

Pas besoin d'utiliser des opérateurs pour pirater l'analyseur. Quand un nouveau venu arrive sur le projet, je veux l'immerger dans la logique métier de l'application, et non le nourrir d'explications d'où tous ces points d'exclamation et vides ont été espionnés. Il y a aussi une deuxième entrée entre crochets classique et un commentaire intéressant de Crockford à ce sujet.


L'apparition de la syntaxe des classes dans ES6 n'était pas accompagnée des modificateurs d'accès habituels. Et parfois, le développeur veut faire pipi sur les cours et respecter la confidentialité. Ce qui conduit à ce code Frankenstein:


 class Person { constructor(name) { let _name = name; this.getName = function() { return _name; } } toString() { return `Hello, ${this.getName()}`; } } 

En d'autres termes, les accesseurs sont créés pour l'instance dans le constructeur et la confidentialité est obtenue par leur accès aux variables de propriété locales via la fermeture. Cet exemple ressemble assez à Lacconcino, mais il s'agit d'une approche totalement non évolutive, à moins que vous ne construisiez une solution de framework documentée autour d'elle. Messieurs, utilisons soit les classes disponibles (et attendons la standardisation des champs privés), soit le module de pattern populaire. Pour créer une sorte de solution de mixage intermédiaire, c'est une chose pour vous, car les classes cessent d'être des classes et le code est intelligible.


Pour résumer, il partagera son bon sens avec le guide de style adopté dans le projet, la configuration pour le linter, ou simplement codera des fragments avec des collègues qui contribuent son composant non JavaScript au projet. La langue offre plusieurs options pour littéralement chaque tâche typique, donc améliorer la compréhension les uns des autres et tomber sous un dénominateur commun n'est pas difficile (ou presque).


Mésaventure


Ce sujet est certainement holistique et il y a beaucoup plus d'exemples, mais le message principal de l'article est que vous ne devez pas abuser de la non-évidence en JavaScript où cela peut être évité. La nature de la langue est unique: elle vous permet d'écrire des solutions à la fois élégantes et expressives (modérément "pointues"), compréhensibles et accessibles à tous. Je suis fondamentalement en désaccord avec la sagesse conventionnelle selon laquelle JavaScript "se punissait" ou "enfoui sous une pile de bonnes intentions et d'erreurs". Parce que maintenant la plupart de l'étrangeté est démontrée non pas par le langage, mais par la culture des développeurs et (non) indifférents qui se forment autour de lui.

Source: https://habr.com/ru/post/fr432102/


All Articles