Il modo migliore per organizzare il codice jQuery / JavaScript (2013) [chiuso]


104

Il problema

Questa risposta ha già ricevuto una risposta, ma è vecchia e non aggiornata. Ho più di 2000 righe di codice in un singolo file e, come tutti sappiamo, questa è una cattiva pratica, specialmente quando sto cercando nel codice o aggiungendo nuove funzionalità. Voglio organizzare meglio il mio codice, per ora e per il futuro.

Devo menzionare che sto costruendo uno strumento (non un semplice sito Web) con molti pulsanti, elementi dell'interfaccia utente, trascinamento, rilascio, ascoltatori / gestori di azioni e funzioni nell'ambito globale in cui diversi ascoltatori possono utilizzare la stessa funzione.

Codice di esempio

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

... // Around 75 more of this

function update_html(){ .... }

...

Altro codice di esempio

Conclusione

Ho davvero bisogno di organizzare questo codice per il miglior utilizzo e per non ripetermi ed essere in grado di aggiungere nuove funzionalità e aggiornare quelle vecchie. Ci lavorerò da solo. Alcuni selettori possono essere 100 righe di codice, altri sono 1. Ho guardato un po ' require.jse l'ho trovato un po ' ripetitivo, e in realtà ho scritto più codice del necessario. Sono aperto a qualsiasi possibile soluzione che soddisfi questi criteri e il collegamento a risorse / esempi sono sempre un vantaggio.

Grazie.


Se vuoi aggiungere backbone.js e require.js, sarà molto lavoro.
jantimon

1
Quali compiti ti ritrovi a fare più e più volte mentre scrivi questo?
Mike Samuel


4
Impara Angular! È il futuro.
Onur Yıldırım

2
Il tuo codice non dovrebbe trovarsi su un link esterno, dovrebbe essere qui. Inoltre, @codereview è un posto migliore per questi tipi di domande.
George Stocker

Risposte:


98

Esaminerò alcune semplici cose che potrebbero, o meno, aiutarti. Alcuni potrebbero essere ovvi, altri potrebbero essere estremamente arcani.

Passaggio 1: compartimentare il codice

Separare il codice in più unità modulari è un ottimo primo passo. Raccogli ciò che funziona "insieme" e inseriscili nella loro piccola unità racchiusa. non preoccuparti del formato per ora, tienilo in linea. La struttura è un punto successivo.

Quindi, supponi di avere una pagina come questa:

inserisci qui la descrizione dell'immagine

Avrebbe senso suddividere in compartimenti in modo che tutti i gestori / raccoglitori di eventi relativi all'intestazione siano presenti, per facilitare la manutenzione (e non dover setacciare 1000 righe).

Puoi quindi utilizzare uno strumento come Grunt per ricostruire il tuo JS in una singola unità.

Passaggio 1a: gestione delle dipendenze

Usa una libreria come RequireJS o CommonJS per implementare qualcosa chiamato AMD . Il caricamento asincrono del modulo ti consente di dichiarare esplicitamente da cosa dipende il tuo codice, il che ti consente quindi di scaricare la chiamata della libreria nel codice. Puoi letteralmente dire "Questo richiede jQuery" e AMD lo caricherà ed eseguirà il tuo codice quando jQuery sarà disponibile .

Anche questo ha una gemma nascosta: il caricamento della libreria verrà effettuato non appena il DOM sarà pronto, non prima. Questo non interrompe più il caricamento della tua pagina!

Passaggio 2: modulare

Vedi il wireframe? Ho due unità pubblicitarie. Molto probabilmente avranno ascoltatori di eventi condivisi.

Il tuo compito in questo passaggio è identificare i punti di ripetizione nel tuo codice e cercare di sintetizzare tutto questo in moduli . I moduli, in questo momento, comprenderanno tutto. Divideremo le cose man mano che procediamo.

L'idea principale di questo passaggio è di andare dal passaggio 1 ed eliminare tutte le copie-pasta, per sostituirle con unità che sono vagamente accoppiate. Quindi, invece di avere:

ad_unit1.js

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

ad_unit2.js

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

Avro:

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();

Ciò ti consente di compartimentalizzare tra i tuoi eventi e il tuo markup oltre a sbarazzarti della ripetizione. Questo è un passaggio abbastanza decente e lo estenderemo ulteriormente in seguito.

Passaggio 3: scegli un framework!

Se desideri modulare e ridurre ulteriormente le ripetizioni, ci sono un sacco di fantastici framework in giro che implementano approcci MVC (Model - View - Controller). Il mio preferito è Backbone / Spine, tuttavia, c'è anche Angular, Yii, ... La lista potrebbe continuare.

Un modello rappresenta i tuoi dati.

Una vista rappresenta il tuo mark-up e tutti gli eventi ad esso associati

Un controller rappresenta la tua logica aziendale, in altre parole, il controller dice alla pagina quali visualizzazioni caricare e quali modelli utilizzare.

Questo sarà un passo di apprendimento significativo, ma il premio ne vale la pena: privilegia il codice pulito e modulare rispetto agli spaghetti.

Ci sono molte altre cose che puoi fare, queste sono solo linee guida e idee.

Modifiche specifiche del codice

Di seguito sono riportati alcuni miglioramenti specifici al codice:

 $('.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);

            }

        }

    });
 });

Questo è meglio scritto come:

$("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);
          }
     });
});

All'inizio del codice:

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

All'improvviso, hai un modo per creare un livello standard da qualsiasi punto del tuo codice senza copiare e incollare. Lo stai facendo in cinque posti diversi. Ti ho appena salvato cinque copie.

Ancora uno:

// Wrapper della serie di regole per le azioni

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

Questo è un modo molto potente per registrare le regole se hai eventi che non sono standard o eventi di creazione. Questo è anche seriamente kick-ass se combinato con un sistema di notifica pub / sub e quando è associato a un evento si attiva ogni volta che si creano elementi. Associazione di eventi modulari Fire'n'forget!


2
@ Jessica: perché uno strumento online dovrebbe essere diverso? L'approccio è sempre lo stesso: compartimentalizzare / modularizzare, promuovere l'accoppiamento libero tra i componenti utilizzando un framework (tutti vengono con la delega degli eventi in questi giorni), dividere il codice. Cosa c'è che non si applica al tuo strumento lì? Il fatto che tu abbia molti pulsanti?
Sébastien Renauld

2
@Jessica: aggiornato. Ho semplificato e razionalizzato la creazione di Layer utilizzando un concetto simile a un file View. Così. In che modo questo non si applica al tuo codice?
Sébastien Renauld

10
@ Jessica: Dividere in file senza ottimizzare è come acquistare più cassetti per archiviare spazzatura. Un giorno, devi svuotare ed è più facile svuotare prima che i cassetti si riempiano. Perché non fare entrambe le cose? In questo momento, sembra che si vorrà un layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.jsse si sta solo andando a dividere il codice in su. Utilizzare gruntper ricostruirli in uno e Google Closure Compilerper compilare e ridurre al minimo.
Sébastien Renauld

3
Quando si utilizza require.js, è necessario esaminare anche l'ottimizzatore r.js, questo è ciò che rende require.js degno di essere utilizzato. Combinerà
Willem D'Haeseleer

2
@ SébastienRenauld la tua risposta e i tuoi commenti sono ancora molto apprezzati dagli altri utenti. Se questo può farti sentire meglio;)
Adrien Be

13

Ecco un modo semplice per dividere il codice corrente in più file, utilizzando require.js. Ti mostrerò come dividere il tuo codice in due file. L'aggiunta di più file sarà semplice dopo.

Passaggio 1) Nella parte superiore del codice, crea un oggetto App (o qualsiasi nome preferisci, come MyGame):

var App = {}

Passaggio 2) Converti tutte le variabili e le funzioni di primo livello in modo che appartengano all'oggetto App.

Invece di:

var selected_layer = "";

Tu vuoi:

App.selected_layer = "";

Invece di:

function getModified(){
...
}

Tu vuoi:

App.getModified = function() {

}

Nota che a questo punto il tuo codice non funzionerà finché non avrai completato il passaggio successivo.

Passaggio 3) Converti tutte le variabili globali e i riferimenti alle funzioni per passare attraverso l'app.

Cambia cose come:

selected_layer = "."+classes[1];

per:

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

e:

getModified()

per:

App.GetModified()

Passaggio 4) Verifica il tuo codice in questa fase: dovrebbe funzionare tutto. Probabilmente all'inizio riceverai alcuni errori perché ti sei perso qualcosa, quindi correggili prima di andare avanti.

Passaggio 5) Imposta requirejs. Presumo che tu abbia una pagina web, servita da un server web, il cui codice è in:

www/page.html

e jquery in

www/js/jquery.js

Se questi percorsi non sono esattamente come questo, quanto segue non funzionerà e dovrai modificare i percorsi.

Scarica requirejs e metti require.js nella tua www/jsdirectory.

nel tuo page.html, elimina tutti i tag di script e inserisci un tag di script come:

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

creare www/js/main.jscon il contenuto:

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

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

quindi inserisci tutto il codice che hai appena sistemato nei passaggi 1-3 (la cui unica variabile globale dovrebbe essere App) in:

www/js/app.js

All'inizio di quel file, metti:

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

In fondo metti:

})

Quindi carica page.html nel tuo browser. La tua app dovrebbe funzionare!

Passaggio 6) Crea un altro file

Qui è dove il tuo lavoro paga, puoi farlo ancora e ancora.

Estrai del codice da www/js/app.jsche fa riferimento a $ e App.

per esempio

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

Metterlo in www/js/foo.js

All'inizio di quel file, metti:

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

In fondo metti:

})

Quindi cambia l'ultima riga di www / js / main.js in:

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

Questo è tutto! Fallo ogni volta che vuoi inserire del codice nel suo file!


Questo ha diversi problemi: quello ovvio è che stai frammentando tutti i tuoi file alla fine e costringendo 400 byte di dati sprecati a ogni utente per script per caricamento pagina non utilizzando il r.jspreprocessore. Inoltre, non hai effettivamente affrontato il problema dell'OP - hai semplicemente fornito un require.jshowto generico .
Sébastien Renauld

7
Eh? La mia risposta è specifica a questa domanda. E r.js è ovviamente il passo successivo, ma il problema qui è l'organizzazione, non l'ottimizzazione.
Lyn Headley,

Mi piace questa risposta, non ho mai usato require.js quindi dovrò vedere se posso usarlo e trarne beneficio. Uso molto il pattern del modulo, ma forse questo mi permetterà di astrarre alcune cose e quindi richiederle.
Tony

1
@ SébastienRenauld: questa risposta non riguarda solo require.js. Parla principalmente di avere uno spazio dei nomi per il codice che stai costruendo. Penso che dovresti apprezzare le parti buone e apportare una modifica in caso di problemi. :)
mithunsatheesh

10

Per la tua domanda e i tuoi commenti, presumo che tu non sia disposto a portare il tuo codice in un framework come Backbone o utilizzare una libreria di caricamento come Require. Vuoi solo un modo migliore per organizzare il codice che hai già, nel modo più semplice possibile.

Capisco che sia fastidioso scorrere oltre 2000 righe di codice per trovare la sezione su cui si desidera lavorare. La soluzione è dividere il codice in file diversi, uno per ciascuna funzionalità. Ad esempio sidebar.js, canvas.jsecc. Quindi puoi unirli insieme per la produzione usando Grunt, insieme a Usemin puoi avere qualcosa del genere:

Nel tuo html:

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

Nel tuo Gruntfile:

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

Se vuoi usare Yeoman ti darà un codice boilerplate per tutto questo.

Quindi, per ogni file stesso, è necessario assicurarsi di seguire le migliori pratiche e che tutto il codice e le variabili siano tutti in quel file e non dipendano da altri file. Questo non significa che non puoi chiamare le funzioni di un file da un altro, il punto è avere variabili e funzioni incapsulate. Qualcosa di simile alla spaziatura dei nomi. Presumo che tu non voglia portare tutto il tuo codice in modo che sia orientato agli oggetti, ma se non ti dispiace refactoring un po ', ti consiglio di aggiungere qualcosa di equivalente a quello che viene chiamato un modello di modulo. Assomiglia a questo:

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
  }
})();

Quindi puoi caricare questo bit di codice in questo modo:

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

Ciò ti consentirà di avere un codice molto più gestibile senza dover riscrivere troppo il codice.


1
Potresti voler riconsiderare seriamente quel penultimo frammento, che non è meglio che avere codice scritto in linea: il tuo modulo richiede #sidebar #sortable. Puoi anche risparmiare memoria inserendo il codice e salvando i due IETF.
Sébastien Renauld

Il punto è che puoi usare qualsiasi codice di cui hai bisogno. Sto solo usando un esempio dal codice originale
Jesús Carrera,

Sono d'accordo con Gesù, questo è solo un esempio, l'OP può facilmente aggiungere un "oggetto" di opzioni che consentirebbe loro di specificare il selettore dell'elemento invece di codificarlo, ma questo era solo un rapido esempio. Vorrei dire che adoro il pattern del modulo, è il pattern principale che uso, ma anche con quello detto sto ancora cercando di organizzare meglio il mio codice. Uso normalmente C # quindi la denominazione e la creazione della funzione sembrano così generiche. Cerco di mantenere un "pattern" come i trattini bassi sono locali e privati, le variabili sono solo "oggetti" e quindi faccio riferimento alla funzione nel mio ritorno che è pubblica.
Tony

Tuttavia trovo ancora difficoltà con questo approccio e desidero avere un modo migliore per farlo. Ma funziona molto meglio della semplice dichiarazione delle mie variabili e funzioni nello spazio globale per causare conflitti con altri js .... lol
Tony

6

Usa javascript MVC Framework per organizzare il codice javascript in modo standard.

I migliori framework MVC JavaScript disponibili sono:

La selezione di un framework MVC JavaScript richiedeva tanti fattori da considerare. Leggi il seguente articolo di confronto che ti aiuterà a selezionare il miglior framework in base ai fattori importanti per il tuo progetto: http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

È inoltre possibile utilizzare RequireJS con il framework per supportare il caricamento di file e moduli js asincroni.
Guarda quanto segue per iniziare a caricare il modulo JS:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/


4

Classifica il tuo codice. Questo metodo mi sta aiutando molto e funziona con qualsiasi 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();
    });
})();

Nel tuo editor preferito (il migliore è Komodo Edit) puoi piegare comprimendo tutte le voci e vedrai solo i titoli:

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

2
+1 per una soluzione JS standard che non si basa sulle librerie.
hobberwickey

-1 per molteplici ragioni. Il tuo codice equivalente è esattamente lo stesso dell'OP ... + uno IETF per "sezione". Inoltre, stai usando selettori eccessivamente ampi senza consentire agli sviluppatori di moduli di sovrascrivere la creazione / rimozione di quelli o di estendere il comportamento. Infine, gli IETF non sono gratuiti.
Sébastien Renauld

@hobberwickey: Non so voi, ma preferisco fare affidamento su qualcosa che è guidato dalla comunità e dove i bug verranno trovati rapidamente se posso. Soprattutto se fare altrimenti mi condanna a reinventare la ruota.
Sébastien Renauld

2
Tutto questo sta organizzando il codice in sezioni discrete. L'ultima volta che ho verificato che era A: buona pratica e B: non qualcosa per cui hai davvero bisogno di una libreria supportata dalla comunità. Non tutti i progetti si adattano a Backbone, Angular, ecc. E la modularizzazione del codice avvolgendolo in funzioni è una buona soluzione generale.
hobberwickey

È possibile ogni volta che si desidera fare affidamento su qualsiasi libreria preferita per utilizzare questo approccio. Ma la soluzione di cui sopra funziona con puro javascript, librerie personalizzate o qualsiasi famoso framework js

3

Io suggerirei:

  1. modello editore / sottoscrittore per la gestione degli eventi.
  2. orientamento agli oggetti
  3. namespacing

Nel tuo caso Jessica, dividi l'interfaccia in pagine o schermate. Le pagine o gli schermi possono essere oggetti ed estesi da alcune classi padre. Gestisci le interazioni tra le pagine con una classe PageManager.


Puoi ampliarlo con esempi / risorse?
Kivylius

1
Cosa intendi per "orientamento agli oggetti"? Quasi tutto in JS è un oggetto. E non ci sono classi in JS.
Bergi

2

Ti suggerisco di usare qualcosa come Backbone. Backbone è una libreria JavaScript RESTFUL supportata. Ik rende il tuo codice più pulito e più leggibile ed è potente se usato insieme a requirejs.

http://backbonejs.org/

http://requirejs.org/

Backbone non è una vera libreria. Ha lo scopo di dare struttura al tuo codice javascript. È in grado di includere altre librerie come jquery, jquery-ui, google-maps ecc. Backbone è a mio parere l'approccio javascript più vicino alle strutture Object Oriented e Model View Controller.

Anche per quanto riguarda il tuo flusso di lavoro .. Se costruisci le tue applicazioni in PHP usa la libreria Laravel. Funzionerà perfettamente con Backbone se utilizzato con il principio RESTfull. Di seguito il collegamento a Laravel Framework e un tutorial sulla creazione di API RESTfull:

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

http://laravel.com/

Di seguito è riportato un tutorial da nettuts. Nettuts ha molti tutorial di alta qualità:

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


0

Forse è giunto il momento di iniziare a implementare un intero flusso di lavoro di sviluppo utilizzando strumenti come yeoman http://yeoman.io/ . Ciò ti aiuterà a controllare tutte le tue dipendenze, il processo di compilazione e, se lo desideri, i test automatici. È molto lavoro per iniziare, ma una volta implementato renderà le modifiche future molto più facili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.