Session Technique Vendredi 9 Septembre 2016¶
Je vais vous parler de JavaScript, des problèmes auxquels j'ai été confronté et des solutions que j'ai apporté.
Vous trouverez projets.zip en fichier joint, dedans se trouve des fichiers qui illustre ou explique les différentes parties ci-dessous, je vous invite à le télécharger et le décompresser.
Chaque fois ou je ferai allusion à un des fichiers se trouvant à l'intérieur vous verrez projets:nom_du_fichier
Petite introduction¶
JavaScript est un langage à prototype bien méconnu. On dit que l'on peut faire de l'objet avec JavaScript sans vraiment comprendre ce que prototype peut bien vouloir signifier.
La programmation orientée prototype est une forme de programmation orientée objet sans classe, basée sur la notion de prototype. Un prototype est un objet à partir duquel on crée de nouveaux objets. Dans le langage de programmation orientée prototype Self, les propriétés d'un objet, qu'ils renvoient à des attributs ou à des méthodes, sont appelés slots ; il n'y a pas la distinction entre les slots de données et les slots de code qu'on a avec les classes. La grande différence avec la programmation objet à classes est qu'on peut remplacer le contenu des slots, en ajouter d'autres ou changer la hiérarchie d'héritage que cela soit prévu dans l'objet original ou pas.
Self fut le premier langage à prototypes. Il a été conçu dans les laboratoires de Sun dans les années 1990. Le plus connu actuellement est JavaScript. Source Wikipedia.org
Les types¶
JavaScript, c'est bien connu, est un langage non typé. Cela ne signifie pas qu'il est impossible de faire la différence entre eux mais rien n'est fait pour nous facilité la tâche.Deux moyen complémentaire pour différencier les types :
- typeof, cet opérateur donne le type "simple" d'une variable ; sous forme de string il nous dit si la variable est un "string", "number", "boolean", "object", "undefined". Les tableaux en JavaScript sont considéré comme des objets ainsi que la valeur null.
- instanceof, cet opérateur compare une instance à un constructeur et dit si oui ou non l'instance peut-être une instance de ce constructeur. Notion qui peut être flou car tout est Object et, si l'on intègre l'héritage, cela peut rester très imprécis.
Petit exemple - projets:types.html
Comparaisons¶
Il y a 2 moyen de comparer en JavaScript :- == qui fait une comparaison de valeur avec conversion implicite des deux parties sous format numérique
- === qui fait une comparaison de valeur et de type (string, number, boolean, object - avec une comparaison des références pour les objets et non des valeurs).
Petit exemple - projets:types.html
Tout sa pour dire qu'il peut être une véritable entreprise de déterminer le type bien précis d'un objet en JavaScript.
Les développeurs de CytoscapeJS, pour différencier les edges des nodes, ne se sont pas prie la tête et on tout simplement mis en place une propriété 'group' pour les objets qu'on leur donne qui à pour valeur 'nodes' ou 'edges'. En réalité on peut donné des objets à CytoscapeJS sans pour autant préciser le 'group' de l'élément, CytoscapeJS va alors déduire de quel 'group' appartient l'élément.
Héritage¶
Revenons sur le prototype, commençons par une petite comparaison Objets à Classes vs Objets à Prototypes.
Objets à Classes¶
- Une Classe définie par son code source est statique,
- Elle représente une définition abstraite de l'objet,
- Tout objet est instance d'une classe,
- L'héritage se situe au niveau des classes.
Objets à Prototypes¶
- Un prototype défini par son code source est mutable,
- Il est lui même un objet au même titre que les autres,
- Il a donc une existence physique en mémoire,
- Il peut être modifié, appelé, etc...
- Il est obligatoirement nommé,
- Un prototype peut être vu comme un exemplaire modèle d'une famille d'objet,
- Un objet hérite des propriétés (valeurs et méthodes) de son prototype.
Source Wikipedia.org
Pourquoi ?¶
La grande difficulté avec CytoscapeJS provenait de la gestion des éléments. Les différentes fonctionnalités implémenté permettant le filtrage, le regroupements de nodes, etc... il est clair que la gestion des éléments n'est pas la même que pour un simple affichage de graphe tant il y a de manipulation possible.CytoscapeJS incorpore déjà des fonctionnalités de manipulation des éléments : possibilité de les enlever du graphe, de les déplacer (mettre des nodes dans un groupe, changer la target/source des edges, etc...).
On pourrait croire que c'est suffisant mais à cela vient s'ajouter le coût en ressource des ces changements, surtout pour de nombreux nodes ; en effet lorsque CytoscapeJS déplace, enlève ou ajoute des éléments dans le graphe, il fait immédiatement le rendu. Pour un node il n'y a pas de vrai problème, cela reste assez fluide, mais pour une dizaine ... voire une centaine ...
J'ai donc opté pour une solution maison et ai développé plusieurs objets :
- ElementJSON qui gère les structures d'éléments (groupé ou non) ainsi que le filtrage, ainsi qu'un autre problème non énoncé plus haut : le calcul d'impact
- elementDefinition qui concerne les éléments pris séparément, comment sont stocké les informations de chaque élément et comment y accéder, les modifier, etc...
La partie qui nous concerne ici se trouve dans le fichier elementDefinition car un node et un edge sont très similaire.
Voir CytoscapeJS.
Les fonctions¶
Avant de faire de l'héritage, il faut comprendre comment JavaScript gère les fonctions, comment les appeler, etc...
Pour cela je vous invite à ouvrir le fichier projets:fonctions.html.
J'y ai mis des explications, des logs, etc..., n'hésitez pas à vous servir du débogueur de votre navigateur (touche F12 pour ouvrir la console, cliquez sur l'onglet source - chrome - ou scripts - firefox - pour mettre des point d'arrêt et recharer la page pour voir ce qu'il se passe).
Héritage et Chaîne de Prototype¶
Maintenant que l'on sait utilier les fonctions plus intimement, on peut faire de l'héritage et comprendre ce qu'il se passe.
Explications dans projets:heritage.html
Exemple d'utilisation de l'héritage dans projets:sampleElementDefinition.js
Threading¶
JavaScript est MONO-thread ; on ne peut donc pas faire de multi-thread. Par chance JavaScript est EVENEMENTIELLE et on peut en tiré parti pour faire du code asynchrone.
Cela permet de "simuler" le threading et de faire du "code non-bloquant". Faire du code non-bloquant est une chose très importante notamment pour ce qui voudrais ce frotter à NodeJS mais nous y viendrons plus tard.
Le principe est donc de prendre une partie de son code et de la déplacer dans le temps avant de l'exécuter.
Pourquoi ?¶
Pour éviter de "freezer" l'écran sur un ajustement graphique. Lors de l'utilisation du graphe, l'utilisateur peut être susceptible de vouloir un affichage par groupe des nodes et de vouloir cacher le détail des groupes (garder le node groupe et enlever ceux qui sont à l'intérieur). Il peut "Collapse" le groupe qu'il veut ou tous d'un coup. S'il "Collapse" tous les groupes et que l'on ne fait pas de traitement asynchrone, la page freeze et l'utilisateur ne peut plus rien faire hors cela peut durer plusieurs secondes voire plusieurs minutes selon le nombre de groupe et de node. De plus, si le traitement prend trop de temps, le navigateur pourrait interpréter ce temps anormalement long comme une erreur dans le script et afficher de lui même un message invitant l'utilisateur à arrêter le script.
Comment ?¶
Il existe deux fonction permettant de faire de l'asynchrone :- setTimeout(fn, time) qui est appelé une seule foi
- setInterval(fn, time) qui renvoie un numérique (id d'intervalle) et qui est appelé en boucle
Il existe cependant des pièges à l'utilisation de setInterval donc mieux vaut creuser la question avant de l'utiliser "naïvement".
Explication dans projets:horloge.html
Exemple d'implémentation dans projets:sampleGraphe.js
Package¶
La notion de package n'existe pas en JavaScript, pas comme en JAVA par exemple, cependant il existe une manière d'organiser son code pour s'en rapprocher.
Scopes¶
Pour ce faire, on va jouer sur les scopes ; donc que sont ses "scopes" ?¶
En JavaScript, tout est public et appartient au scope globale, si l'on ne précise pas "var" lors de la déclaration d'une variable on ajoute juste une propriété au scope globale (qui est l'objet window). Les scopes s'entremêlent très facilement et si l'on ne sait pas ce que l'on fait, il est très facile d'ajouté des propriétés et des variables au "mauvais" scope ; de même il est très aisé de manipuler les propriétés et variable d'un scope supérieur.
En fait JavaScript est un grand fainéant et très permissif, s'il ne trouve pas ce qu'on lui demande dans le scope local, il va chercher dans le scope supérieur et ainsi de suite jusqu'a trouver quelque chose qui s'en rapproche et s'il ne trouve rien, il à tendance à nous donné undefined au lieu de nous dire que cela n'existe pas et à nous de nous débrouillé avec.
Comment créer un scope ?¶
C'est très simple, il suffit de créer un fonction et de l'appeler. Lors de l'appel d'une fonction, JavaScript cré un nouveau scope qui correspond à la fonction. Si cette fonction est appelé à l'intérieur d'une fonction, un nouveau scope est créé à l'intérieur de cette fonction. Ce qui fait une chaîne de scope un peu comme pour le chaînage de prototype et appelé une variable va le faire chercher du scope local vers le scope globale.
Explication dans projets:scope.html et dans projets:scope-avance.html ATTENTION: tous ce passe dans la console - avec commentaire.
Exemple d'implémentation dans projets:sampleElementDefinition.js vous l'avez déjà regardé pour l'implémentation de l'héritage, faîtes plus attention à getElements et au trois dernières lignes.
NodeJS¶
Dans NodeJS intervient la notion de package car chaque module est importé là où on l'utilise.
Exemple : pour créer un serveur http avec NodeJS on import le module HTTP.
var http = require("http"); // Import d'un module en NodeJS.
Exemple de création d'un module et d'import de celui-ci dans projets:export.js et projets:import.js.
C'est la fin, pour ceux qui voudrait en savoir plus, on peut s'arranger des séances techniques sur le thème de votre choix.
N'hésitez pas à me contacter : jonathan.moutarde@cc.in2p3.fr