IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JavaScript Discussion :

Injection réfléchie de dépendances


Sujet :

JavaScript

  1. #1
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut Injection réfléchie de dépendances
    Salut les codeurs,


    Je voulais partager avec vous une technique assez peu connue pour injecter dynamiquement des scripts et gérer des dépendances. Je ne me souviens pas qu'on en ait parlé sur dvpz.com, ou si c'est le cas, je suis passé à côté. Cette technique est utilisée notamment par AngularJS, vous savez, le framework JS le plus hype de toute l'histoire de 2014 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    function MonController($scope, $http, monmoduleA, monmoduleB) {
      // ...
    }

    Quand j'ai commencé à utiliser AngularJS, ce mode de déclaration des dépendances a été un grand mystère pour moi, et reste toujours assez contre-intuitif même avec le temps. Les fonctions contrôleurs prennent un nombre quelconque d'arguments, dans n'importe quel ordre, et seul le nom des variables locales déterminent quelles dépendances sont chargées. Le nom d'une variable locale ? Mes réflexes de vieux codeur JS me disent que ça n'a aucune importance, car c'est seulement utile dans le corps de la fonction.


    Eh bien non, que nenni, ce n'est plus vrai quand on bosse avec la propriété de réflexion des fonctions en JavaScript. Réflexion, ça ne veut pas dire qu'elles se creusent les neurones pour faire le boulot à notre place. C'est ma traduction de "reflective" en anglais, que j'aurais aussi pu traduire par "réfléchissement" ou encore "introspection". Ça signifie simplement qu'une fonction, lors de son exécution, a accès à son propre code la définissant :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    (function HAL(){
         console.log("Bonjour, je suis "+arguments.callee.toString());
    })();

    C'est une facette assez méconnue du langage et pourtant fichtrement intéressante. Car si une fonction a accès a son propre code, elle peut s'auto-analyser, voire pourquoi pas s'auto-réécrire et détruire le monde et injecter du code selon le nom des variables locales qui y sont déclarées.


    Finalement, le mécanisme tel qu'implémenté par Angular se révèle assez simple. Voilà comment récupérer dans une array la liste des noms de variables locales d'une fonction en argument :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    function getVarLocales(scope){
       var deps = scope.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
      console.log(deps);  
    }
     
     
    getVarLocales(function(toto,tata,titi){ }); // affiche ["toto","tata","titi"]

    A partir de là, c'est un jeu d'enfant de brancher ça à votre script loader favori (vous n'en avez pas ? je balance des noms en vrac: LABJS-browserify-require-headJS-Lazyload-jQuery.getScript-webpack-taFonctionPersoToiMemeTuSais)


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function include(scope){
       var deps = scope.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
      console.log(deps);
     
      tonScriptLoader(deps, function(resolvedDeps){
        scope.apply(this, resolvedDeps);
      });
     
    }
     
     
    include(function(myLib, myModule){
      console.log("myLib et myModule chargés et prêts", myLib, myModule);
    });

    Un inconvénient est que les noms de fichiers doivent être des noms de variable valides, donc pas d'espaces, de points ou de chiffres en premier caractère. Chose que l'on peut arranger facilement en faisant évoluer la syntaxe comme ceci par exemple:


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function include(scope, paths){
       var deps = scope.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
      console.log(deps);
     
      tonScriptLoader(deps, paths, function(resolvedDeps){
        scope.apply(this, resolvedDeps);
      });
     
    }
     
     
    include(function(myLib, myModule){
      console.log("myLib et myModule chargés et prêts", myLib, myModule);
    }, {
      myLib: "../lib/maLib.min.js",
      myModule : "42 is the answer.js"
    });

    Voilà, pas besoin de bosser chez Google pour obtenir un système d'injection de dépendances supra-simple à utiliser. Ah, mais il y a quand même deux gros bémols :
    1) les minifieurs, ces outils qui compressent le code en renommant à la sauvage tout ce qui traîne, ne font qu'une bouchée de vos beaux noms de variables locales. Et vous allez vous retrouver à charger des modules a.js b.js c.js... C'est pour ça qu'Angular a dû amener une autre syntaxe à base de String littérales, mais alors on revient à la case départ et la DI réflechie n'a plus beaucoup d'intérêt...
    2) ça fait toujours autant de fichiers à charger côté client. Et en attendant HTTP2, il est toujours conseillé de concaténer ses scripts en gros paquets pour économiser sur la latence réseau. Autrement dit, les préprocesseurs tels que browserify ou requireJS optimizer restent à ce jour le top en matière de perf.


    Bref, pas une révolution mais toujours sympa à connaître et utile pour de l'injection de dépendances vite fait bien fait sur des petits projets

  2. #2
    Expert éminent
    Avatar de sekaijin
    Homme Profil pro
    Urbaniste
    Inscrit en
    Juillet 2004
    Messages
    4 205
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Urbaniste
    Secteur : Santé

    Informations forums :
    Inscription : Juillet 2004
    Messages : 4 205
    Points : 9 127
    Points
    9 127
    Par défaut
    Citation Envoyé par SylvainPV Voir le message
    Salut les codeurs,
    ...
    C'est une facette assez méconnue du langage et pourtant fichtrement intéressante. Car si une fonction a accès a son propre code, elle peut s'auto-analyser, voire pourquoi pas s'auto-réécrire et détruire le monde et injecter du code selon le nom des variables locales qui y sont déclarées.
    ...
    non pas détruire le monde.
    j'utilise cette technique lorsque j'ai une fonction qui doit faire quelque chose au premier appel
    et autre chose les fois suivantes.

    Lors du premier appel la fonction est définie pour faire la première action (initi souvent), et la fonction s'auto redéfini pour que les fois suivant elle fasse la deuxième action.

    en faisant ainsi il n'y a pas de test. pas besoin la première fois vu que le code est celui de la première action c'est que la fonction n'a jamais été appelé, pas besoin de test les suivantes vu que le code est celui de la deuxième action c'est que la fonction a déjà été exécuté une première fois.

    normalement on ferrait un test à chaque passage pour choisir entre la première ou la deuxième action.
    Si le test est complexe on garderait un booléen et testerait le booléen mais à chaque passage du code devenu obsolète serait exécuté.

    Je tiens cette technique de mes années de travail en IA. c'est la forme la plus basique d'apprentissage.
    L'auto programmation est un champs ouvert à quasi tout les langages interprété et JS ne coupe pas à la règle. c'est d'un abord capilotracté par fois. ça rend le code étonnant. ça demande de la vigilance. et ce n'est pas toujours facile à reprendre par quelqu'un d'externe au dev. Mais c'est d'une concision et efficacité redoutable. dans le cas décrit lorsque on débogue le code de la fonction est parfois si simple qu'on ce demande comment ça marche. (simplement en ayant abandonné en cours d'exécution des pan entier de code.)

    A+JYT
    PS: ExtJS est lui aussi friands de cette technique.

  3. #3
    Membre éprouvé

    Profil pro
    Inscrit en
    Juin 2007
    Messages
    748
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 748
    Points : 1 022
    Points
    1 022
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    scope.apply(this, resolvedDeps);

    c'est la l'astuce, oupa... mais c'est pas ça qui fait des dépendances entre widget ou entre module etc... resolvedDeps est t'il scpopé lui même de facon a être sympa? et comment enregistrer des datas dans ce scope, la tu ne dit rien, et je trouve ca assez naif, bien mais naif, car les modules doivent communiquer assez rapidement et cette version ne le permet pas

  4. #4
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Le fonctionnement interne des scripts loaders n'est pas le sujet, j'ai fait exprès de ne pas en parler. Les scripts loaders cités savent gérer les arbres de dépendances, chacun à leur manière. Je voulais uniquement expliquer comment on peut se servir intelligemment des noms de variables locales en utilisant la propriété de réflexivité des fonctions en JavaScript. La gestion de modules est le meilleur exemple qui m'est venu, mais comme en a parlé sekaijin, les applications vont bien au-delà.

Discussions similaires

  1. [Framework] Injection d'une dépendance initialisée avec passage dynamique d'arguments
    Par Samouraï virtuel dans le forum Spring
    Réponses: 1
    Dernier message: 24/12/2009, 12h48
  2. [EJB3] [JBoss] Injection de dépendance circulaire ?
    Par Claythest dans le forum Java EE
    Réponses: 6
    Dernier message: 04/08/2009, 08h11
  3. [EJB3] Injection de dépendance et Stateful
    Par newbeewan dans le forum Java EE
    Réponses: 1
    Dernier message: 15/05/2007, 07h33
  4. [Integration] [EasyMock] Injection de dépendance à l'éxécution
    Par frangin2003 dans le forum Spring
    Réponses: 2
    Dernier message: 06/03/2007, 11h06
  5. Spring + TagSupport et injection de dépendance
    Par worldchampion57 dans le forum Spring Web
    Réponses: 2
    Dernier message: 26/02/2007, 09h01

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo