Améliorez vos connaissances JavaScript en lisant le code source
Vous souvenez-vous de la première fois que vous avez plongé dans le code source d'une bibliothèque ou d'un framework que vous utilisez fréquemment? Pour moi, ce moment est venu lors de mon premier emploi en tant que développeur frontend il y a trois ans.
Nous venions de terminer la réécriture d'un cadre interne hérité de la création de cours en ligne. Au début de la réécriture, nous avions examiné de nombreuses solutions, notamment Mithril, Inferno, Angular, React, Aurelia, Vue et Polymer. Comme j'étais très débutant (je venais de passer du journalisme au développement Web), je me souviens de m'être senti intimidé par la complexité de chaque cadre et de ne pas comprendre comment chacun fonctionnait.
Ma compréhension a grandi lorsque j'ai commencé à étudier le cadre choisi. Mithril, plus en profondeur. Depuis lors, ma connaissance du JavaScript – et de la programmation en général – a été grandement facilitée par les heures que j'ai passées à fouiller dans les entrailles des bibliothèques que j'utilise quotidiennement, au travail ou dans mes propres projets. Dans cet article, je vais vous expliquer comment utiliser votre bibliothèque ou framework préféré et l'utiliser comme outil pédagogique.

Les avantages du code source en lecture
L'un des principaux avantages de la lecture du code source est le nombre de choses que vous pouvez apprendre. Lorsque j’ai examiné pour la première fois le code de Mithril, j’avais une vague idée de ce que le DOM virtuel était. Lorsque j'ai terminé, je suis reparti en sachant que le DOM virtuel est une technique qui consiste à créer une arborescence d'objets décrivant l'apparence de votre interface utilisateur. Cet arbre est ensuite transformé en éléments DOM à l'aide d'API DOM telles que document.createElement
. Les mises à jour sont effectuées en créant une nouvelle arborescence décrivant l'état futur de l'interface utilisateur, puis en la comparant avec les objets de l'ancienne arborescence.
J'avais lu tout cela dans divers articles et didacticiels. Pouvoir l'observer au travail dans le contexte d'une application que nous avions livrée m'a beaucoup éclairé. Cela m'a également appris quelles questions poser lors de la comparaison de différents cadres. Au lieu de regarder les stars de GitHub, par exemple, je savais maintenant que je devais poser des questions telles que «Comment la manière dont chaque framework effectue les mises à jour affecte-t-elle les performances et l'expérience utilisateur?»
Un autre avantage est l'augmentation de votre appréciation et de votre compréhension de bonne architecture d'application. Alors que la plupart des projets open source suivent généralement la même structure avec leurs référentiels, chacun d'entre eux présente des différences. La structure de Mithril est plutôt plate et si vous connaissez bien son API, vous pouvez faire des suppositions éclairées sur le code dans des dossiers tels que rend
routeur
et à la demande
. D'autre part, la structure de React reflète sa nouvelle architecture. Les responsables ont séparé le module responsable des mises à jour de l'interface utilisateur ( de réagir-réconciliateur
) du module responsable de la restitution des éléments DOM ( de réagir-dom
).
L'un des avantages de cette c’est qu’il est maintenant plus facile pour les développeurs d’écrire leurs propres moteurs de rendu personnalisés en s’accrochant au paquet de réactif-réconciliateur
. Parcel, un bundler de modules que j'ai étudié récemment, possède également un dossier packages
comme React. Le module clé s'appelle parcel-bundler
et contient le code responsable de la création des ensembles, de la mise en rotation du serveur du module actif et de l'outil de ligne de commande.

Un autre avantage – qui m’a été une bonne surprise – est que vous devenez plus à l'aise en lisant la spécification JavaScript officielle qui définit le fonctionnement du langage. La première fois que j’ai lu la spécification, c’était quand j’enquêtais sur la différence entre et
). J'ai examiné la question car j'ai remarqué que Mithril avait utilisé et nouvelle
(nouvelle erreur throw Error
dans la mise en œuvre de sa fonction m
et je me demandais s'il y avait un avantage à l'utiliser par throw new error
. Depuis lors, j'ai également appris que les opérateurs logiques &&
et ||
ne renvoient pas nécessairement des booléens ont trouvé les règles qui régissent la manière dont le ==
L'opérateur d'égalité force les valeurs et la raison Object.prototype.toString.call ({})
renvoie '[object Object]'
.
. Techniques de lecture du code source
Il existe de nombreuses façons d’approcher le code source. J'ai trouvé le moyen le plus simple de commencer consiste à sélectionner une méthode dans la bibliothèque de votre choix et à documenter ce qui se passe lorsque vous l'appelez. Ne documentez pas chaque étape, mais essayez d'identifier son flux et sa structure globaux.
Je l'ai récemment fait avec ReactDOM.render
et, par conséquent, beaucoup appris sur React Fiber et sur certaines des raisons de sa mise en œuvre. Heureusement, React étant un framework populaire, j'ai rencontré beaucoup d'articles écrits par d'autres développeurs sur le même sujet, ce qui a accéléré le processus.
Cette plongée en profondeur m'a également présenté les concepts de coopérative. ordonnancement méthode de window.requestIdleCallback
et exemple concret de listes chaînées (React gère les mises à jour en les plaçant dans une liste chaînée de mises à jour hiérarchisées). Ce faisant, il est conseillé de créer une application très basique en utilisant la bibliothèque. Cela facilite les choses lors du débogage car vous n'avez pas à traiter les traces de pile causées par d'autres bibliothèques.
Si je ne fais pas un examen approfondi, j'ouvrirai le dossier / node_modules
. dans un projet sur lequel je travaille ou je vais aller dans le référentiel GitHub. Cela se produit généralement lorsque je rencontre un bogue ou une fonctionnalité intéressante. Lors de la lecture du code sur GitHub, assurez-vous de lire la dernière version. Vous pouvez afficher le code des commits avec la dernière balise de version en cliquant sur le bouton utilisé pour changer de branche et sélectionner «balises». Les bibliothèques et les frameworks subissent des modifications permanentes, vous ne voulez donc pas en savoir plus sur quelque chose qui pourrait être supprimé dans la prochaine version.
Une autre façon moins compliquée de lire le code source est ce que j'aime appeler la méthode du «regard superficiel». Dès que j'ai commencé à lire le code, j'ai installé express.js ouvert son dossier / node_modules
et parcouru ses dépendances. Si le README
ne m'a pas fourni d'explication satisfaisante, je lis la source. Cela m'a conduit à ces découvertes intéressantes:
- Express dépend de deux modules qui fusionnent les objets mais le font de manière très différente.
merge-descriptors
ajoute uniquement les propriétés directement trouvées sur l'objet source et fusionne également les propriétés non énumérables, tandis queutils-fusion
ne fait qu'itérer les propriétés énumérables d'un objet ainsi que celles trouvées dans sa chaîne de prototype.merge-descriptors
utiliseObject.getOwnPropertyNames ()
etObject.getOwnPropertyDescriptor ()
tandis queutils-merge
utilise pour le moment. dans ; - Le module
setprototypeof
fournit un moyen multiplate-forme de paramétrage du prototype d'un objet instancié; -
escape-html
est un module à 78 lignes pour échappe à une chaîne de contenu pour qu'elle puisse être interpolée en HTML.
Bien que les résultats ne soient probablement pas utiles immédiatement, il est utile de comprendre de manière générale les dépendances utilisées par votre bibliothèque ou votre framework.
pour déboguer du code frontal, les outils de débogage de votre navigateur sont votre meilleur ami. Entre autres choses, ils vous permettent d’arrêter le programme à tout moment et d’en inspecter l’état, de sauter l’exécution d’une fonction ou d’y entrer ou en sortir. Parfois, cela ne sera pas immédiatement possible car le code a été minifié. J'ai tendance à le désarchiver et à copier le code non minié dans le fichier correspondant du dossier / node_modules
.

Étude de cas: la fonction de connexion de Redux
React-Redux est une bibliothèque utilisée pour gérer l’état des applications React. Lorsque je traite avec des bibliothèques populaires telles que celles-ci, je commence par rechercher des articles écrits sur sa mise en œuvre. En faisant cela pour cette étude de cas, je suis tombé sur cet article . C'est une autre bonne chose à propos de la lecture du code source. La phase de recherche vous mène généralement à des articles informatifs tels que celui-ci qui améliorent seulement votre propre pensée et votre compréhension.
connect
est une fonction de React-Redux qui relie les composants de React au magasin Redux d’une application. Comment? Eh bien, selon la docs il fait ce qui suit:
"… renvoie une nouvelle classe de composants connectés qui englobe le composant que vous avez passé."
Après avoir lu ceci, je voudrais posez les questions suivantes:
- Est-ce que je connais des modèles ou des concepts dans lesquels les fonctions prennent une entrée, puis renvoient cette même entrée avec des fonctionnalités supplémentaires?
- Si je connais de tels modèles, comment pourrais-je l'implémenter en fonction l'explication donnée dans la documentation?
Généralement, l'étape suivante consiste à créer un exemple d'application très basique utilisant connect
. Cependant, à cette occasion, j'ai choisi d'utiliser la nouvelle application React que nous construisons à Limejump parce que je voulais comprendre connectez-vous
dans le contexte d'une application qui sera éventuellement utilisée pour la production. environnement.
Le composant sur lequel je me concentre est le suivant:
La classe MarketContainer étend le composant {
// code omis par souci de brièveté
}
const mapDispatchToProps = dispatch => {
revenir {
updateSummary: (résumé, début, aujourd'hui) => dispatch (updateSummary (résumé, début, aujourd'hui))
}
}
exportez la connexion par défaut (null, mapDispatchToProps) (MarketContainer);
Il s'agit d'un composant conteneur qui englobe quatre composants connectés plus petits. L'une des premières choses que vous rencontrez dans le fichier qui exporte la méthode connect
est le commentaire suivant: connect est une façade sur connectAdvanced . Sans aller loin, nous avons notre premier moment d'apprentissage: l'occasion d'observer le motif de la façade en action . À la fin du fichier, nous voyons que connect
exporte l'invocation d'une fonction appelée createConnect
. Ses paramètres sont un tas de valeurs par défaut qui ont été déstructurées comme ceci:
export function createConnect ({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {})
Encore une fois, nous découvrons un autre moment d'apprentissage: l'exportation des fonctions invoquées et des arguments de fonction par défaut de déstructuration . La déstructuration est un moment d’apprentissage car le code avait été écrit ainsi:
export function createConnect ({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
})
Cela aurait entraîné cette erreur Non capturé TypeError: impossible de détruire la propriété 'connectHOC' de 'indéfini' ou 'null'.
C’est parce que la fonction n’a pas d’argument par défaut sur lequel s’appuyer.
Note : Pour en savoir plus, lisez l’article de David Walsh . Certains moments d'apprentissage peuvent sembler anodins, selon votre connaissance de la langue, et il peut donc être préférable de vous concentrer sur des choses que vous n'avez pas vues auparavant ou sur lesquelles vous devez en apprendre davantage.
createConnect
lui-même ne fait rien dans son corps de fonction. Il retourne une fonction appelée connect
celle que j'ai utilisée ici:
export default connect (null, mapDispatchToProps) (MarketContainer)
Il faut quatre arguments, tous facultatifs, et les trois premiers arguments passent chacun par une fonction match
qui permet de définir leur comportement en fonction de la présence des arguments et du type de valeur. Maintenant, comme le deuxième argument fourni pour match
est l'une des trois fonctions importées dans connect
je dois décider quel fil suivre
Il y a des moments d'apprentissage avec le ] fonction proxy utilisée pour encapsuler le premier argument de connect
si ces arguments sont des fonctions, l'utilitaire isPlainObject
utilisé pour rechercher des objets simples ou l'avertissement
module qui révèle comment vous pouvez définir votre débogueur sur break sur toutes les exceptions . Après les fonctions de match, nous arrivons à connectHOC
la fonction qui prend notre composant React et le connecte à Redux. C’est une autre invocation de fonction qui renvoie wrapWithConnect
la fonction qui gère réellement la connexion du composant au magasin.
En regardant l’implémentation de connectHOC
je peux comprendre pourquoi elle a besoin de. connectez-vous
pour masquer les détails de sa mise en œuvre. C'est le cœur de React-Redux et contient une logique qui n'a pas besoin d'être exposée via connect
. Même si je mettrais fin à la plongée profonde ici, si j'avais continué, ce serait le moment idéal pour consulter le document de référence que j'ai trouvé précédemment, car il contient une explication incroyablement détaillée de la base de code.
Résumé
Lecture du code source C'est difficile au début, mais comme avec n'importe quoi, cela devient plus facile avec le temps. Le but n'est pas de tout comprendre, mais de partir avec une perspective différente et de nouvelles connaissances. La clé consiste à délibérer sur l'ensemble du processus et à être extrêmement curieux à propos de tout.
Par exemple, j'ai trouvé la fonction isPlainObject
intéressante car elle utilise cette fonction if (typeof obj! == 'object '|| obj === null) retourne false
pour s'assurer que l'argument donné est un objet simple. Quand j'ai lu pour la première fois son implémentation, je me suis demandé pourquoi il n'utilisait pas Object.prototype.toString.call (opts)! == '[object Object]'
qui est moins de code et distingue les objets des objets sub types tels que l'objet Date. Cependant, la lecture de la ligne suivante a révélé que, dans le cas extrêmement improbable où un développeur utilisant connect
renvoie un objet Date, par exemple, cela sera géré par le Object.getPrototypeOf (obj) === null
check.
Une autre intrigue dans est isPlainObject
est le code suivant:
while (Object.getPrototypeOf (baseProto)! == null) {
baseProto = Object.getPrototypeOf (baseProto)
}
Une recherche sur Google m'a conduit à ce fil [StackOverflow] et le numéro Redux expliquant comment ce code traite les cas tels que la vérification des objets provenant d'un iFrame.
Liens utiles Sur la lecture du code source

Source link