Meilleure façon d'organiser le code jQuery / JavaScript (2013) [fermé]

104

Le problème

Cette réponse a déjà reçu une réponse, mais elle est ancienne et n'est pas à jour. J'ai plus de 2000 lignes de code dans un seul fichier, et comme nous le savons tous, c'est une mauvaise pratique, surtout lorsque je regarde du code ou que j'ajoute de nouvelles fonctionnalités. Je veux mieux organiser mon code, pour le moment et pour le futur.

Je dois mentionner que je construis un outil (pas un simple site Web) avec beaucoup de boutons, d'éléments d'interface utilisateur, de glisser-déposer, d'écouteurs / gestionnaires d'action et de fonction dans le cadre global où plusieurs écouteurs peuvent utiliser la même fonction.

Exemple de code

$('#button1').on('click', function(e){
    // Determined action.
    update_html();
});

... // Around 75 more of this

function update_html(){ .... }

...

Plus d'exemple de code

Conclusion

J'ai vraiment besoin d'organiser ce code pour une meilleure utilisation et de ne pas me répéter et de pouvoir ajouter de nouvelles fonctionnalités et mettre à jour les anciennes. J'y travaillerai moi-même. Certains sélecteurs peuvent contenir 100 lignes de code, d'autres sont 1. J'ai regardé un peu require.jset j'ai trouvé que c'était un peu répétitif, et j'écrivais en fait plus de code que nécessaire. Je suis ouvert à toute solution possible qui correspond à ces critères et les liens vers des ressources / exemples sont toujours un plus.

Merci.

Kivylius
la source
Si vous voulez ajouter backbone.js et require.js, ce sera beaucoup de travail.
jantimon
1
Quelles tâches vous surprenez-vous à faire encore et encore en écrivant ceci?
Mike Samuel
4
Avez-vous visité codereview.stackexchange.com ?
Antony
4
Apprenez Angular! C'est le futur.
Onur Yıldırım
2
Votre code ne doit pas être sur un lien externe, il doit être ici. De plus, @codereview est un meilleur endroit pour les types de questions.
George Stocker

Réponses:

98

Je vais passer en revue certaines choses simples qui peuvent ou non vous aider. Certains pourraient être évidents, certains pourraient être extrêmement obscurs.

Étape 1: compartimenter votre code

Séparer votre code en plusieurs unités modulaires est une très bonne première étape. Rassemblez ce qui fonctionne «ensemble» et mettez-les dans leur propre petite unité encastrée. ne vous inquiétez pas du format pour le moment, gardez-le en ligne. La structure est un point ultérieur.

Alors, supposons que vous ayez une page comme celle-ci:

entrez la description de l'image ici

Il serait judicieux de compartimenter afin que tous les gestionnaires / classeurs 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 seule unité.

Étape 1a: Gestion des dépendances

Utilisez une bibliothèque telle que RequireJS ou CommonJS pour implémenter quelque chose appelé AMD . Le chargement de module asynchrone vous permet d'indiquer explicitement de quoi dépend votre code, ce qui vous permet ensuite de décharger l'appel de bibliothèque vers le code. Vous pouvez simplement dire littéralement "Cela nécessite jQuery" et AMD le chargera et exécutera votre code lorsque jQuery sera disponible .

Cela a également un petit bijou caché: le chargement de la bibliothèque sera effectué dès que le DOM sera prêt, pas avant. Cela n'interrompt plus le chargement de votre page!

Étape 2: Modulariser

Voir le filaire? J'ai deux blocs d'annonces. Ils auront probablement des auditeurs 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 synthétiser tout cela en modules . Les modules, pour le moment, engloberont tout. Nous allons diviser les choses au fur et à mesure.

L'idée générale de cette étape est de passer à partir de l'étape 1 et de supprimer tous les copiés-pâtes, de les remplacer par des unités 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() {
     ... Events go here
 }

page.js:

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

Ce qui vous permet de compartimenter entre vos événements et votre balisage en plus de vous débarrasser de la répétition. C'est une étape assez décente et nous l'étendrons plus tard.

Étape 3: Choisissez un cadre!

Si vous souhaitez modulariser et réduire encore plus les répétitions, il existe de nombreux frameworks impressionnants qui implémentent des approches MVC (Model - View - Controller). 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 balisage et tous les événements qui lui sont associés

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

Ce sera une étape d'apprentissage importante, mais le prix en vaut la peine: il favorise un code propre et modulaire par rapport aux spaghettis.

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

Modifications 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(){

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

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

                            var name = "ln_"+reply.split(' ').join('_');
                            var 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() {
             // There must be a way to get the input from here using this, if it is a standard library. If you wrote your own, make the value retrievable using something other than a class selector (horrible performance + scoping +multiple instance issues)

             // This is where the view comes into play. Instead of cloning, bind the rendering into a JS prototype, and instantiate it. It means that you only have to modify stuff in one place, you don't risk cloning events with it, and you can test your Layer stand-alone
             var newLayer = new Layer();
             newLayer
               .setName(name)
               .bindToGroup(parent);
          }
     });
});

Plus tôt dans votre code:

window.Layer = function() {
    this.instance = $("<div>");
    // Markup generated here
};
window.Layer.prototype = {
   setName: function(newName) {
   },
   bindToGroup: function(parentNode) {
   }
}

Soudainement, vous avez un moyen de créer une couche standard à partir de n'importe où dans votre code sans copier-coller. Vous faites cela à cinq endroits différents. Je viens de vous sauver cinq copies-pâtes.

Un de plus:

// wrapper d'ensemble de règles pour les actions

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

var 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"));

// If you need to add elements later on, you can just call GlobalRules.run(yourNewElement);

C'est un moyen très efficace d'enregistrer des règles si vous avez des événements qui ne sont pas standard ou des événements de création. Ceci est également très difficile lorsqu'il est combiné avec un système de notification de pub / sous et lorsqu'il est lié à un événement que vous déclenchez chaque fois que vous créez des éléments. Reliure événementielle modulaire Fire'n'forget!

Sébastien Renauld
la source
2
@Jessica: pourquoi un outil en ligne devrait-il être différent? L'approche est toujours la même: compartimenter / modulariser, favoriser le couplage lâche entre les composants à l'aide d'un framework (ils sont tous livrés avec la délégation d'événements ces jours-ci), séparer votre code. Qu'y a-t-il qui ne s'applique pas à votre outil là-bas? Le fait que vous ayez plein de boutons?
Sébastien Renauld
2
@Jessica: mis à jour. J'ai simplifié et rationalisé la création de couches en utilisant un concept similaire à un View. Alors. Comment cela ne s'applique-t-il pas à votre code?
Sébastien Renauld
10
@Jessica: Diviser en fichiers sans optimiser, c'est comme acheter plus de tiroirs pour stocker les déchets. Un jour, vous devez vider, et c'est plus facile de vider avant que les tiroirs ne se remplissent. Pourquoi ne pas faire les deux? En ce moment, on dirait que vous aurez besoin d' un layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.jssi vous allez juste diviser votre code vers le haut. Utilisez gruntpour les reconstruire en un seul et Google Closure Compilerpour compiler et minimiser.
Sébastien Renauld
3
Lorsque vous utilisez require.js, vous devez également regarder dans l'optimiseur r.js, c'est vraiment ce qui fait que require.js vaut la peine d'être utilisé. Il combinera et optimisera tous vos fichiers: requirejs.org/docs/optimization.html
Willem D'Haeseleer
2
@ SébastienRenauld votre réponse et vos commentaires sont toujours très appréciés par les autres utilisateurs. Si cela peut vous faire vous sentir mieux;)
Adrien Be
13

Voici un moyen simple de diviser votre base de code actuelle en plusieurs fichiers, à l'aide de require.js. Je vais vous montrer comment diviser votre code en deux fichiers. L'ajout de fichiers supplémentaires sera simple après cela.

Étape 1) En haut de votre code, créez un objet App (ou le nom de votre choix, comme MyGame):

var App = {}

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

Au lieu de:

var selected_layer = "";

Tu veux:

App.selected_layer = "";

Au lieu de:

function getModified(){
...
}

Tu veux:

App.getModified = function() {

}

Notez qu'à ce stade, votre code ne fonctionnera pas tant que vous n'aurez pas terminé l'étape suivante.

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

Changer des choses comme:

selected_layer = "."+classes[1];

à:

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

et:

getModified()

à:

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 continuer.

Étape 5) Configurez requirejs. Je suppose que vous avez une page Web, servie à partir d'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 ça, ci-dessous ne fonctionnera pas et vous devrez modifier les chemins.

Téléchargez requirejs et mettez require.js dans votre www/jsrépertoire.

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

<script data-main="js/main" src="js/require.js"></script>

créer www/js/main.jsavec du contenu:

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

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

puis mettez 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

Tout en haut de ce fichier, mettez:

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

En bas, mettez:

})

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

Étape 6) Créez un autre fichier

Voici où votre travail est payant, vous pouvez le faire encore et encore.

Extrayez du code à partir de www/js/app.jscette référence $ et App.

par exemple

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

Mettre dans www/js/foo.js

Tout en haut de ce fichier, mettez:

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

En bas, mettez:

})

Puis 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!

Lyn Headley
la source
Cela pose plusieurs problèmes - le plus évident étant que vous fragmentez tous vos fichiers à la fin et que vous forcez 400 octets de données gaspillées à chaque utilisateur par script par chargement de page en n'utilisant pas le r.jspréprocesseur. De plus, vous n'avez pas réellement abordé le problème du PO - vous avez simplement fourni un require.jshowto générique .
Sébastien Renauld
7
Hein? 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.
Lyn Headley
J'aime cette réponse, je n'ai jamais utilisé require.js donc je vais devoir voir si je peux l'utiliser et en tirer un avantage. J'utilise beaucoup le modèle de module mais cela me permettra peut-être d'abstraire certaines choses et de les obliger à entrer.
Tony
1
@ SébastienRenauld: cette réponse ne concerne pas seulement require.js. Cela parle principalement d'avoir un espace de noms pour le code que vous construisez. Je pense que vous devriez apprécier les bonnes parties et faire une modification si vous trouvez des problèmes avec elle. :)
mithunsatheesh
10

Pour votre question et vos commentaires, je suppose que vous n'êtes pas disposé à porter votre code vers un framework tel que 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 faire défiler plus de 2000 lignes de code pour trouver la section sur laquelle vous voulez travailler. La solution consiste à diviser votre code en différents fichiers, un pour chaque fonctionnalité. Par exemple sidebar.js, canvas.jsetc. Ensuite, vous pouvez les réunir pour la production en utilisant Grunt, avec Usemin, vous pouvez avoir quelque chose comme ceci:

Dans votre html:

<!-- build:js scripts/app.js -->
<script src="scripts/sidebar.js"></script>
<script src="scripts/canvas.js"></script>
<!-- endbuild -->

Dans votre Gruntfile:

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

Si vous souhaitez utiliser Yeoman, il vous donnera un code standard pour tout cela.

Ensuite, pour chaque fichier lui-même, vous devez vous assurer que vous suivez les meilleures pratiques et que tout le code et les variables sont tous dans ce fichier et ne dépendent pas d'autres fichiers. Cela ne signifie pas que vous ne pouvez pas appeler les fonctions d'un fichier à partir d'un autre, le but est d'avoir des variables et des fonctions encapsulées. Quelque chose de similaire à l'espacement des noms. Je suppose que vous ne voulez pas porter tout votre code pour qu'il soit orienté objet, mais si cela ne vous dérange pas de refactoriser un peu, je vous recommande d'ajouter quelque chose d'équivalent avec ce qu'on appelle un modèle de module. Cela ressemble à quelque chose comme ceci:

sidebar.js

var Sidebar = (function(){
// functions and vars here are private
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 {
   // here your can put your "public" functions
   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 à trop réécrire votre code.

Jesús Carrera
la source
1
Vous voudrez peut-être reconsidérer sérieusement cet avant-dernier extrait de code, ce qui n'est pas mieux que d'avoir du code écrit en ligne: votre module nécessite #sidebar #sortable. Vous pouvez également économiser de la mémoire en insérant simplement le code et en enregistrant les deux IETF.
Sébastien Renauld
Le fait est que vous pouvez utiliser le code dont vous avez besoin. J'utilise juste un exemple du code original
Jesús Carrera
Je suis d'accord avec Jésus, ce n'est qu'un exemple, l'OP peut facilement ajouter une option "objet" qui leur permettrait de spécifier le sélecteur de l'élément au lieu de le coder en dur, mais ce n'était qu'un exemple rapide. Je voudrais dire que j'aime le modèle de module, c'est le modèle principal que j'utilise, mais même avec cela, j'essaie toujours de mieux organiser mon code. J'utilise C # normalement, donc la dénomination et la création de fonctions semblent tellement génériques. J'essaie de garder un "motif" comme les traits de soulignement sont locaux et privés, les variables ne sont que des "objets" et ensuite je référence la fonction dans mon retour qui est publique.
Tony
Je trouve cependant toujours des défis avec cette approche et le désir d'avoir une meilleure façon de le faire. Mais cela fonctionne beaucoup mieux que de simplement déclarer mes variables et mes fonctions dans l'espace global pour provoquer des conflits avec d'autres js .... lol
Tony
6

Utilisez javascript MVC Framework afin d'organiser le code javascript de manière standard.

Les meilleurs frameworks JavaScript MVC disponibles sont:

La sélection d'un framework JavaScript MVC nécessitait de nombreux facteurs à prendre en compte. Lisez l'article de comparaison suivant qui vous aidera à sélectionner le meilleur cadre 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 et de modules Asynchrounous js.
Regardez ci-dessous pour commencer le chargement du module JS:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

Tailleur Rohit
la source
4

Catégorisez votre code. Cette méthode m'aide beaucoup et fonctionne avec n'importe quel framework js:

(function(){//HEADER: menu
    //your code for your header
})();
(function(){//HEADER: location bar
    //your code for your location
})();
(function(){//FOOTER
    //your code for your footer
})();
(function(){//PANEL: interactive links. e.g:
    var crr = null;
    $('::section.panel a').addEvent('click', function(E){
        if ( crr) {
            crr.hide();
        }
        crr = this.show();
    });
})();

Dans votre éditeur préféré (le meilleur est Komodo Edit), vous pouvez vous replier en réduisant toutes les entrées et vous ne verrez que les titres:

(function(){//HEADER: menu_____________________________________
(function(){//HEADER: location bar_____________________________
(function(){//FOOTER___________________________________________
(function(){//PANEL: interactive links. e.g:___________________

la source
2
+1 pour une solution JS standard qui ne repose pas sur des bibliothèques.
hobberwickey
-1 pour plusieurs raisons. Votre code équivalent est exactement le même que celui des OP ... + un IETF par "section". De plus, vous utilisez des sélecteurs trop larges sans permettre aux développeurs de modules de remplacer la création / suppression de ceux-ci ou d'étendre le comportement. Enfin, les IETF ne sont pas gratuits.
Sébastien Renauld
@hobberwickey: Je ne sais pas pour vous, mais je préfère me fier à quelque chose qui est axé sur la communauté et où les bogues seront trouvés rapidement si je le peux. Surtout si faire autrement me condamne à réinventer la roue.
Sébastien Renauld
2
Tout ce que cela fait, c'est organiser le code en sections discrètes. La dernière fois que j'ai vérifié, c'était A: bonne pratique, et B: pas quelque chose pour lequel vous avez vraiment besoin d'une bibliothèque prise en charge par la communauté. Tous les projets ne s'intègrent pas dans Backbone, Angular, etc. et la modularisation du code en l'enveloppant dans des fonctions est une bonne solution générale.
hobberwickey
Il est possible à tout moment de s'appuyer sur n'importe quelle bibliothèque préférée pour utiliser cette approche. Mais la solution ci-dessus fonctionne avec du javascript pur, des bibliothèques personnalisées ou tout autre framework js célèbre
3

Je voudrais suggerer:

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

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

Fazle Rabbi
la source
Pouvez-vous développer cela avec des exemples / ressources?
Kivylius
1
Qu'entendez-vous par «orientation objet»? Presque tout dans JS est un objet. Et il n'y a pas de classes dans JS.
Bergi
2

Je vous suggère d'utiliser quelque chose comme Backbone. Backbone est une bibliothèque javascript prise en charge par RESTFUL. Ik rend votre code plus propre et plus lisible et est puissant lorsqu'il est utilisé avec requirejs.

http://backbonejs.org/

http://requirejs.org/

Backbone n'est pas une vraie bibliothèque. Il est destiné à donner une structure à votre code javascript. Il est capable d'inclure d'autres bibliothèques comme jquery, jquery-ui, google-maps, etc. Backbone est à mon avis l'approche javascript la plus proche des structures Object Oriented et Model View Controller.

En ce qui concerne également votre flux de travail .. Si vous construisez vos applications en PHP utilisez la bibliothèque Laravel. Il fonctionnera parfaitement avec Backbone lorsqu'il est utilisé avec le principe RESTfull. Ci-dessous le lien vers Laravel Framework et un tutoriel sur la création d'API RESTfull:

http://maxoffsky.com/code-blog/building-restful-api-in-laravel-start-here/

http://laravel.com/

Vous trouverez ci-dessous un tutoriel de nettuts. Nettuts propose de nombreux tutoriels de haute qualité:

http://net.tutsplus.com/tutorials/javascript-ajax/understanding-backbone-js-and-the-server/

Chris Visser
la source
0

Il est peut-être temps pour vous de commencer à implémenter tout un flux de travail de développement en utilisant des outils tels que yeoman http://yeoman.io/ . Cela vous aidera à contrôler toutes vos dépendances, le processus de construction et, si vous le souhaitez, des tests automatisés. C'est beaucoup de travail pour commencer, mais une fois mis en œuvre, les changements futurs seront beaucoup plus faciles.

Drobson
la source