106 votes

Meilleure façon d'organiser le code jQuery/JavaScript (2013)

Le Problème

Cette réponse a déjà été donnée auparavant mais est obsolète et n'est pas à jour. J'ai plus de 2000 lignes de code dans un seul fichier, et comme nous le savons tous, ce n'est pas une bonne pratique, surtout lorsque je passe en revue le code ou que j'ajoute de nouvelles fonctionnalités. Je veux mieux organiser mon code, pour le moment et pour l'avenir.

Je devrais mentionner que je construis un outil (pas un simple site web) avec beaucoup de boutons, éléments d'interface utilisateur, drag and drops, écouteurs/actionneurs et fonctions dans la portée globale où plusieurs écouteurs peuvent utiliser la même fonction.

Exemple de code

$('#button1').on('click', function(e){
    // Action déterminée.
    update_html();
});

... // Environ 75 lignes supplémentaires de ce type

function update_html(){ .... }

...

Plus d'exemples de code

Conclusion

J'ai vraiment besoin d'organiser ce code pour un meilleur usage et ne pas me répéter, et être capable d'ajouter de nouvelles fonctionnalités et de mettre à jour les anciennes. Je vais travailler sur cela par moi-même. Certains sélecteurs peuvent faire 100 lignes de code, d'autres seulement 1. J'ai jeté un coup d'œil à require.js et j'ai trouvé cela un peu répétitif, et en fait, cela nécessite d'écrire plus de code que nécessaire. Je suis ouvert à toute solution possible qui correspond à ces critères et un lien vers des ressources/exemples est toujours un plus.

Merci.

0 votes

Si vous souhaitez ajouter backbone.js et require.js, cela nécessitera beaucoup de travail.

0 votes

Je suppose que le chargement paresseux de fichiers séparés n'est pas préférable. Vous devrez probablement interrompre l'exécution pour charger les fichiers externes avant de pouvoir procéder. Une autre solution serait de charger simplement le strict minimum initialement et de continuer à charger le reste en arrière-plan.

0 votes

Requirejs avec backbone pourrait toujours être une bonne option juste pour organiser votre code. Je préfère personnellement utiliser des vues pour lier les écouteurs plutôt que d'utiliser jQuery. Ce n'est pas nécessairement beaucoup de mots. Eh bien, juste un avis.

102voto

Sébastien Renauld Points 8624

Je vais passer en revue quelques choses simples qui pourraient, ou pourraient ne pas, vous aider. Certaines peuvent être évidentes, d'autres extrêmement ésotériques.

Étape 1: Cloisonnez votre code

Séparer votre code en plusieurs unités modulaires est un très bon premier pas. Rassemblez ce qui fonctionne "ensemble" et mettez-les dans leur propre petite unité enfermée. Ne vous inquiétez pas du format pour l'instant, gardez-le en ligne. La structure est un point ultérieur.

Donc, supposez que vous avez une page comme celle-ci :

entrer la description de l'image ici

Il serait logique de compartimenter afin que tous les gestionnaires/déclencheurs d'événements liés à l'en-tête soient là, pour faciliter la maintenance (et ne pas avoir à passer au crible 1000 lignes).

Vous pouvez ensuite utiliser un outil tel que Grunt pour reconstruire votre JS en une unité unique.

Étape 1a: Gestion des dépendances

Utilisez une bibliothèque telle que RequireJS or CommonJS pour implémenter quelque chose appelé AMD. Le Chargement Asynchrone de Modules vous permet d'indiquer explicitement sur quoi votre code dépend, ce qui vous permet ensuite de décharger l'appel à la bibliothèque dans le code. Vous pouvez littéralement dire "Cela a besoin de jQuery" et l'AMD le chargera, et exécutera votre code quand jQuery est disponible.

Cela a aussi un joyau caché : le chargement de la bibliothèque se fera dès que le DOM est prêt, pas avant. Cela n'interrompt plus le chargement de votre page !

Étape 2: Modulaire

Voyez la maquette ? J'ai deux unités publicitaires. Ils auront très probablement des écouteurs d'événements partagés.

Votre tâche dans cette étape est d'identifier les points de répétition dans votre code et d'essayer de tout synthétiser en modules. Les modules, pour l'instant, engloberont tout. Nous découperons les choses au fur et à mesure.

Toute l'idée de cette étape est de passer de l'étape 1 et de supprimer tous les copier-coller, pour les remplacer par des unités qui sont faiblement couplées. Donc, au lieu d'avoir :

ad_unit1.js

 $("#au1").click(function() { ... });

ad_unit2.js

 $("#au2").click(function() { ... });

J'aurai :

ad_unit.js:

 var AdUnit = function(elem) {
     this.element = elem || new jQuery();
 }
 AdUnit.prototype.bindEvents = function() {
     ... Les événements vont ici
 }

page.js:

 var AUs = new AdUnit($("#au1,#au2"));
 AUs.bindEvents();

Cela vous permet de compartimenter entre vos événements et votre markup en plus d'éliminer toute répétition. C'est une étape assez décente et nous étendrons cela plus tard.

Étape 3: Choisissez un framework !

Si vous souhaitez moduler et réduire encore plus les répétitions, il existe de nombreux frameworks géniaux qui implémentent des approches MVC (Modèle - Vue - Contrôleur). Mon préféré est Backbone/Spine, cependant, il y a aussi Angular, Yii, ... La liste est longue.

Un Modèle représente vos données.

Une Vue représente votre mise en page et tous les événements qui y sont associés.

Un Contrôleur représente votre logique métier - en d'autres termes, le contrôleur indique à la page quelles vues charger et quels modèles utiliser.

Ce sera une étape d'apprentissage significative, mais le prix en vaut la peine : il favorise un code propre et modulaire par rapport à du code spaghetti.

Il y a beaucoup d'autres choses que vous pouvez faire, ce ne sont que des directives et des idées.

Changements spécifiques au code

Voici quelques améliorations spécifiques à votre code :

 $('.new_layer').click(function(){

    dialog("Create new layer","Enter your layer name","_input", {

            'OK' : function(){

                    let reply = $('.dialog_input').val();

                    if( reply != null && reply != "" ){

                            let name = "ln_"+reply.split(' ').join('_');
                            let parent = "";

                            if(selected_folder != "" ){
                            parent = selected_folder+" .content";
                            }

                            $R.find(".layer").clone()
                            .addClass(name).html(reply)
                            .appendTo("#layer_groups "+parent);

                            $R.find(".layers_group").clone()
                            .addClass(name).appendTo('#canvas '+selected_folder);

            }

        }

    });
 });

Ceci est mieux écrit comme :

$("body").on("click",".new_layer", function() {
    dialog("Create new layer", "Enter your layer name", "_input", {
         OK: function() {
             // Il doit y avoir un moyen d'obtenir l'entrée d'ici en utilisant this, s'il s'agit d'une bibliothèque standard. Si vous l'avez écrit vous-même, rendez la valeur accessible en utilisant autre chose qu'un sélecteur de classe (mauvaises performances + portée + problèmes d'instances multiples)

             // C'est là que la vue entre en jeu. Au lieu de cloner, liez le rendu dans un prototype JS, et instanciez-le. Cela signifie que vous n'avez à modifier les choses qu'à un seul endroit, vous ne risquez pas de cloner des événements avec, et vous pouvez tester votre Couche seule
             let newLayer = new Layer();
             newLayer
               .setName(name)
               .bindToGroup(parent);
          }
     });
});

Plus tôt dans votre code :

window.Layer = function() {
    this.instance = $("

Tout à coup, vous avez un moyen de créer une couche standard de n'importe où dans votre code sans copier-coller. Vous le faites à cinq endroits différents. Je viens de vous épargner cinq copier-coller.

Encore une fois :

// Wrapper de l'ensemble de règles pour les actions

let PageElements = function(ruleSet) {
ruleSet = ruleSet || [];
this.rules = [];
for (let i = 0; i < ruleSet.length; i++) {
    if (ruleSet[i].target && ruleSet[i].action) {
        this.rules.push(ruleSet[i]);
    }
}
}
PageElements.prototype.run = function(elem) {
for (let i = 0; i < this.rules.length; i++) {
    this.rules[i].action.apply(elem.find(this.rules.target));
}
}

let GlobalRules = new PageElements([
{
    "target": ".draggable",
    "action": function() { this.draggable({
        cancel: "div#scrolling, .content",
        containment: "document"
        });
    }
},
{
    "target" :".resizable",
    "action": function() {
        this.resizable({
            handles: "all",
            zIndex: 0,
            containment: "document"
        });
    }
}

]);

GlobalRules.run($("body"));

// Si vous devez ajouter des éléments plus tard, vous pouvez simplement appeler GlobalRules.run(votreNouvelElement);

C'est une manière très puissante d'enregistrer des règles si vous avez des événements qui ne sont pas standard, ou des événements de création. C'est aussi vraiment génial quand combiné avec un système de notification pub/sub et quand lié à un événement que vous déclenchez chaque fois que vous créez des éléments. Liaison événementielle modulaire Fire'n'forget !

1 votes

Super article, pas sûr si vous avez copié/collé mais ce n'est pas axé sur mon application. Cela concerne davantage les sites web et non un outil en ligne dont j'ai parlé ci-dessus dans la description. Merci quand même.

2 votes

@Jessica: pourquoi un outil en ligne devrait-il être différent? L'approche reste la même : compartimenter/modulariser, promouvoir un couplage lâche entre les composants en utilisant un framework (ils viennent tous avec la délégation d'événements de nos jours), diviser votre code en morceaux. Qu'y a-t-il qui ne s'applique pas à votre outil là ? Le fait que vous avez beaucoup de boutons ?

1 votes

P.S: Je ne copie-colle pas. Et si j'avais copié-collé quelque chose, crois-tu vraiment que j'aurais dessiné l'image en utilisant Paint?

13voto

Lyn Headley Points 3884

Voici un moyen simple de diviser votre base de code actuelle en plusieurs fichiers, en utilisant require.js. Je vais vous montrer comment diviser votre code en deux fichiers. Ajouter plus de fichiers sera facile après cela.

Étape 1) En haut de votre code, créez un objet App (ou le nom que vous préférez, comme MyGame) :

var App = {}

Étape 2) Convertissez toutes vos variables et fonctions de premier niveau pour qu'elles appartiennent à l'objet App.

Au lieu de :

var selected_layer = "";

Vous voulez :

App.selected_layer = "";

Au lieu de :

function getModified(){
...
}

Vous voulez :

App.getModified = function() {

}

Notez qu'à ce stade votre code ne fonctionnera pas jusqu'à ce que vous terminiez l'étape suivante.

Étape 3) Convertissez toutes les références de variables et de fonctions globales pour passer par App.

Changez des choses comme :

selected_layer = "."+classes[1];

en :

App.selected_layer = "."+classes[1];

et :

getModified()

en :

App.GetModified()

Étape 4) Testez votre code à ce stade -- tout devrait fonctionner. Vous obtiendrez probablement quelques erreurs au début parce que vous avez manqué quelque chose, alors corrigez-les avant de passer à la suite.

Étape 5) Configurez requirejs. Je suppose que vous avez une page web, servie par un serveur web, dont le code est dans :

www/page.html

et jquery dans

www/js/jquery.js

Si ces chemins ne sont pas exactement comme indiqué ci-dessus, ce qui suit ne fonctionnera pas et vous devrez modifier les chemins.

Téléchargez requirejs et placez require.js dans votre répertoire www/js.

Dans votre page.html, supprimez toutes les balises de script et insérez une balise de script comme suit :

créez www/js/main.js avec le contenu :

require.config({
 "shim": {
   'jquery': { exports: '$' }
 }
})

require(['jquery', 'app']);

en suite placez tout le code que vous venez de corriger dans les Étapes 1-3 (dont la seule variable globale devrait être App) dans :

www/js/app.js

Au tout début de ce fichier, mettez :

require(['jquery'], function($) {

À la fin mettez :

})

Ensuite chargez page.html dans votre navigateur. Votre application devrait fonctionner !

Étape 6) Créez un autre fichier

Voici où votre travail porte ses fruits, vous pouvez le faire encore et encore.

Sortez un peu de code de www/js/app.js qui fait référence à $ et App.

par exemple :

$('a').click(function() { App.foo() }

Placez-le dans www/js/foo.js

Au tout début de ce fichier, mettez :

require(['jquery', 'app'], function($, App) {

À la fin mettez :

})

Ensuite changez la dernière ligne de www/js/main.js en :

require(['jquery', 'app', 'foo']);

C'est tout ! Faites cela chaque fois que vous voulez mettre du code dans son propre fichier !

0 votes

Cela présente plusieurs problèmes - le plus évident étant que vous fragmentez tous vos fichiers à la fin et forcez 400 octets de données gaspillées à chaque utilisateur par script par chargement de page en n'utilisant pas le préprocesseur r.js. De plus, vous n'avez pas réellement abordé le problème de l'OP - vous avez simplement fourni un tutoriel générique require.js.

7 votes

Huh? Ma réponse est spécifique à cette question. Et r.js est évidemment la prochaine étape mais le problème ici est l'organisation, pas l'optimisation.

0 votes

J'aime cette réponse, je n'ai jamais utilisé require.js donc je devrai voir si je peux l'utiliser et en tirer des bénéfices. J'utilise fortement le pattern de module mais peut-être que cela me permettra d'abstraire certaines choses et ensuite de les requérir.

10voto

Jesús Carrera Points 980

Pour vos questions et commentaires, je suppose que vous ne souhaitez pas porter votre code vers un framework comme Backbone, ou utiliser une bibliothèque de chargement comme Require. Vous voulez juste une meilleure façon d'organiser le code que vous avez déjà, de la manière la plus simple possible.

Je comprends que c'est ennuyeux de parcourir plus de 2000 lignes de code pour trouver la section sur laquelle vous voulez travailler. La solution est de diviser votre code en différents fichiers, un pour chaque fonctionnalité. Par exemple sidebar.js, canvas.js, etc. Ensuite, vous pouvez les assembler pour la production en utilisant Grunt, avec Usemin vous pouvez avoir quelque chose comme ceci :

Dans votre html :

Dans votre Gruntfile :

useminPrepare: {
  html: 'app/index.html',
  options: {
    dest: 'dist'
  }
},
usemin: {
  html: ['dist/{,*/}*.html'],
  css: ['dist/styles/{,*/}*.css'],
  options: {
    dirs: ['dist']
  }
}

Si vous voulez utiliser Yeoman, il vous fournira un code d'amorçage pour tout cela.

Ensuite, pour chaque fichier en lui-même, vous devez vous assurer de suivre les bonnes pratiques et que tout le code et toutes les variables se trouvent dans ce fichier, et ne dépendent pas d'autres fichiers. Cela ne signifie pas que vous ne pouvez pas appeler des fonctions d'un fichier à partir d'un autre, le point est d'avoir des variables et des fonctions encapsulées. Quelque chose de similaire au concept de namespace. Je suppose que vous ne voulez pas convertir tout votre code en Orienté Objet, mais si vous ne vous dérangez pas de refacturer un peu, je vous recommande d'ajouter quelque chose d'équivalent à ce qu'on appelle un pattern Module. Cela ressemble à ceci :

sidebar.js

var Sidebar = (function(){
//les fonctions et variables ici sont privées
var init = function(){
  $("#sidebar #sortable").sortable({
            forceHelperSize: true,
            forcePlaceholderSize: true,
            revert: true,
            revert: 150,
            placeholder: "highlight panel",
            axis: "y",
            tolerance: "pointer",
            cancel: ".content"
       }).disableSelection();
  } 
  return {
   // ici vous pouvez mettre vos fonctions "publiques"
   init : init
  }
})();

Ensuite, vous pouvez charger ce morceau de code comme ceci :

$(document).ready(function(){
   Sidebar.init();
   ...

Cela vous permettra d'avoir un code beaucoup plus maintenable sans avoir à le réécrire trop.

1 votes

Vous voudrez peut-être sérieusement reconsidérer ce deuxième extrait de code, qui n'est pas meilleur que d'avoir du code écrit en ligne : votre module requiert #sidebar #sortable. Vous pouvez tout aussi bien économiser de la mémoire en mettant le code en ligne et en sauvegardant les deux IETF.

0 votes

Le point ici est que vous pouvez utiliser n'importe quel code dont vous avez besoin. Je ne fais qu'utiliser un exemple du code original

0 votes

Je suis d'accord avec Jésus, ceci n'est qu'un exemple, l'OP peut facilement ajouter un "objet" d'options qui leur permettrait de spécifier le sélecteur de l'élément au lieu de le coder en dur mais c'était juste un exemple rapide. J'aimerais dire que j'adore le modèle de module, c'est le modèle principal que j'utilise mais même en disant cela, j'essaie toujours d'organiser mon code de manière plus efficace. J'utilise normalement C#, donc le nommage et la création de fonctions semblent très génériques. J'essaie de garder une "méthode" où les soulignements sont locaux et privés, les variables sont juste des "objets" et ensuite je fais référence à la fonction dans mon retour qui est public.

6voto

Rohit Tailor Points 86

Utilisez un framework MVC JavaScript afin d'organiser le code JavaScript de manière standard.

Les meilleurs frameworks JavaScript MVC disponibles sont :

Le choix d'un framework JavaScript MVC nécessite de prendre en compte de nombreux facteurs. Lisez l'article de comparaison suivant qui vous aidera à choisir le meilleur framework en fonction des facteurs importants pour votre projet : http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

Vous pouvez également utiliser RequireJS avec le framework pour prendre en charge le chargement de fichiers & modules js de manière asynchrone.
Regardez ci-dessous pour commencer le chargement des modules JS :
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

3voto

Fazle Rabbi Points 624

Je suggérerais:

  1. le modèle éditeur/abonné pour la gestion des événements.
  2. l'orientation objet
  3. le 'namespacing'

Dans votre cas Jessica, divisez l'interface en pages ou écrans. Les pages ou écrans peuvent être des objets et étendus à partir de certaines classes parent. Gérez les interactions entre les pages avec une classe PageManager.

0 votes

Pouvez-vous développer ceci avec des exemples / ressources?

1 votes

Que voulez-vous dire par "orientation objet"? Presque tout en JS est un objet. Et il n'y a pas de classes en JS.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X