Najlepszy sposób na uporządkowanie kodu jQuery / JavaScript (2013) [zamknięte]


104

Problem

Na tę odpowiedź udzielono już wcześniej odpowiedzi, ale są one stare i nieaktualne. Mam ponad 2000 wierszy kodu w jednym pliku i, jak wszyscy wiemy, jest to zła praktyka, zwłaszcza gdy przeglądam kod lub dodam nowe funkcje. Chcę lepiej zorganizować swój kod, teraz i na przyszłość.

Powinienem wspomnieć, że buduję narzędzie (nie prostą stronę internetową) z wieloma przyciskami, elementami UI, przeciąganiem, upuszczaniem, odbiornikami / obsługą akcji i funkcją w zakresie globalnym, w którym kilku słuchaczy może używać tej samej funkcji.

Przykładowy kod

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

... // Around 75 more of this

function update_html(){ .... }

...

Więcej przykładowego kodu

Wniosek

Naprawdę muszę zorganizować ten kod, aby jak najlepiej go używać i nie powtarzać samego siebie i móc dodawać nowe funkcje i aktualizować stare. Będę nad tym pracował sam. Niektóre selektory mogą składać się ze 100 linii kodu, inne to 1. Przyjrzałem się temu trochę require.jsi stwierdziłem, że jest to trochę powtarzalne i faktycznie piszę więcej kodu niż potrzeba. Jestem otwarty na wszelkie możliwe rozwiązania spełniające te kryteria, a link do zasobów / przykładów jest zawsze na plus.

Dzięki.


Jeśli chcesz dodać backbone.js i require.js, będzie to dużo pracy.
jantimon

1
Jakie zadania ciągle wykonujesz, pisząc to?
Mike Samuel

4
Czy odwiedziłeś stronę codereview.stackexchange.com ?
Antony

4
Dowiedz się Angular! To przyszłość.
Onur Yıldırım

2
Twój kod nie powinien znajdować się na zewnętrznym łączu, powinien być tutaj. Ponadto @codereview jest lepszym miejscem do zadawania tego typu pytań.
George Stocker

Odpowiedzi:


98

Omówię kilka prostych rzeczy, które mogą ci pomóc lub nie. Niektóre mogą być oczywiste, inne mogą być niezwykle tajemnicze.

Krok 1: Rozmieść swój kod

Rozdzielenie kodu na wiele modułowych jednostek to bardzo dobry pierwszy krok. Zbierz to, co działa „razem” i umieść je w ich małych, zamkniętych jednostkach. nie martw się na razie o format, zachowaj go w tekście. Struktura jest późniejszym punktem.

Załóżmy więc, że masz taką stronę:

wprowadź opis obrazu tutaj

Miałoby sens, aby podzielić na przedziały, tak aby wszystkie związane z nagłówkami programy obsługi zdarzeń / segregatory były tam, dla ułatwienia konserwacji (i bez konieczności przesiewania przez 1000 wierszy).

Następnie możesz użyć narzędzia takiego jak Grunt, aby przebudować JS z powrotem do pojedynczej jednostki.

Krok 1a: Zarządzanie zależnościami

Użyj biblioteki, takiej jak RequireJS lub CommonJS, aby zaimplementować coś, co nazywa się AMD . Asynchroniczne ładowanie modułu umożliwia jawne określenie, od czego zależy Twój kod, co następnie umożliwia odciążenie wywołania biblioteki do kodu. Możesz po prostu powiedzieć „To potrzebuje jQuery”, a AMD załaduje je i wykona kod, gdy jQuery będzie dostępne .

Ma to również ukryty klejnot: ładowanie biblioteki nastąpi w momencie, gdy DOM będzie gotowy, a nie wcześniej. To już nie zatrzymuje ładowania Twojej strony!

Krok 2: Modularyzacja

Widzisz szkielet? Mam dwie jednostki reklamowe. Najprawdopodobniej będą mieć wspólne słuchacze wydarzeń.

Twoim zadaniem na tym etapie jest zidentyfikowanie punktów powtórzeń w kodzie i próba zsyntetyzowania tego wszystkiego w moduły . Moduły będą teraz obejmować wszystko. Będziemy dzielić rzeczy w trakcie.

Cała idea tego kroku polega na przejściu od kroku 1 i usunięciu wszystkich kopiowanych makaronów, aby zastąpić je jednostkami, które są luźno powiązane. Więc zamiast mieć:

ad_unit1.js

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

ad_unit2.js

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

Będę miał:

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

Co pozwala ci na oddzielenie wydarzeń od twoich znaczników, a także pozbycie się powtórzeń. To całkiem przyzwoity krok, który później rozszerzymy.

Krok 3: Wybierz ramy!

Jeśli chcesz jeszcze bardziej zmodularyzować i zmniejszyć liczbę powtórzeń, istnieje kilka niesamowitych frameworków, które implementują podejścia MVC (Model - Widok - Kontroler). Moim ulubionym jest Backbone / Spine, ale jest też Angular, Yii, ... Lista jest długa.

Model przedstawia swoje dane.

Zobacz reprezentuje Mark-up i wszystkie zdarzenia związane z nim

Controller reprezentuje logikę biznesową - innymi słowy, regulator mówi strony co do obciążenia i poglądy, co do korzystania z modeli.

Będzie to znaczący krok do nauki, ale nagroda jest tego warta: preferuje czysty, modułowy kod zamiast spaghetti.

Jest wiele innych rzeczy, które możesz zrobić, to tylko wskazówki i pomysły.

Zmiany specyficzne dla kodu

Oto kilka konkretnych ulepszeń kodu:

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

            }

        }

    });
 });

Jest to lepiej napisane jako:

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

Wcześniej w kodzie:

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

Nagle masz sposób na utworzenie standardowej warstwy z dowolnego miejsca w kodzie bez wklejania kopii. Robisz to w pięciu różnych miejscach. Właśnie uratowałem ci pięć pasty do kopiowania.

Jeszcze jeden:

// Opakowanie zestawu reguł dla akcji

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

Jest to bardzo skuteczny sposób rejestrowania reguł, jeśli masz zdarzenia, które nie są standardowe lub zdarzenia tworzenia. Jest to również naprawdę niesamowite w połączeniu z systemem powiadomień pub / sub oraz w przypadku powiązania z wydarzeniem, które uruchamiasz za każdym razem, gdy tworzysz elementy. Fire'n'forget modułowe wiązanie wydarzeń!


2
@Jessica: dlaczego narzędzie internetowe powinno być inne? Podejście jest nadal takie samo: dzielenie / modularyzację, promowanie luźnych powiązań między komponentami za pomocą frameworka (wszystkie są obecnie dostarczane z delegowaniem zdarzeń), dzielenie kodu na części. Co tam nie ma zastosowania do twojego narzędzia? Fakt, że masz mnóstwo przycisków?
Sébastien Renauld

2
@ Jessica: Zaktualizowano. Uprościłem i usprawniłem tworzenie warstw, używając koncepcji podobnej do View. Więc. Jak to nie dotyczy Twojego kodu?
Sébastien Renauld

10
@Jessica: Dzielenie na pliki bez optymalizacji jest jak kupowanie większej liczby szuflad do przechowywania śmieci. Pewnego dnia musisz się posprzątać i łatwiej jest to zrobić, zanim szuflady się zapełnią. Dlaczego nie zrobić obu? Teraz wygląda jak będziesz chcą layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.jsjeśli tylko będzie się podzielić swój kod. Użyj, gruntaby przebudować je w jeden oraz Google Closure Compilerskompilować i zminimalizować.
Sébastien Renauld

3
Korzystając z require.js, trzeba też naprawdę przyjrzeć się optymalizatorowi r.js, to właśnie sprawia, że ​​warto używać require.js. Połączy
Willem D'Haeseleer

2
@ SébastienRenauld Twoja odpowiedź i komentarze są nadal bardzo cenione przez innych użytkowników. Jeśli to sprawi, że poczujesz się lepiej;)
Adrien Be

13

Oto prosty sposób na podzielenie aktualnej bazy kodu na wiele plików przy użyciu require.js. Pokażę ci, jak podzielić kod na dwa pliki. Dodanie większej liczby plików będzie później proste.

Krok 1) U góry kodu utwórz obiekt aplikacji (lub dowolną nazwę, na przykład MyGame):

var App = {}

Krok 2) Przekonwertuj wszystkie zmienne i funkcje najwyższego poziomu, aby należały do ​​obiektu aplikacji.

Zamiast:

var selected_layer = "";

Chcesz:

App.selected_layer = "";

Zamiast:

function getModified(){
...
}

Chcesz:

App.getModified = function() {

}

Zwróć uwagę, że w tym momencie kod nie będzie działał, dopóki nie wykonasz następnego kroku.

Krok 3) Przekonwertuj wszystkie globalne odwołania do zmiennych i funkcji, aby przejść przez aplikację.

Zmień rzeczy, takie jak:

selected_layer = "."+classes[1];

do:

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

i:

getModified()

do:

App.GetModified()

Krok 4) Przetestuj swój kod na tym etapie - wszystko powinno działać. Prawdopodobnie na początku pojawi się kilka błędów, ponieważ coś przeoczyłeś, więc napraw je, zanim przejdziesz dalej.

Krok 5) Skonfiguruj requirejs. Zakładam, że masz stronę internetową obsługiwaną z serwera WWW, której kod znajduje się w:

www/page.html

i jQuery w

www/js/jquery.js

Jeśli te ścieżki nie są dokładnie takie , jak te, poniższe nie będą działać i będziesz musiał je zmodyfikować.

Pobierz requirejs i umieść require.js w swoim www/jskatalogu.

w swoim page.html, usuń wszystkie tagi script i wstaw tag script, na przykład:

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

twórz www/js/main.jsz zawartością:

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

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

następnie umieść cały kod, który właśnie naprawiłeś w krokach 1-3 (którego jedyną zmienną globalną powinna być aplikacja) w:

www/js/app.js

Na samej górze tego pliku umieść:

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

Na dole umieść:

})

Następnie załaduj page.html w swojej przeglądarce. Twoja aplikacja powinna działać!

Krok 6) Utwórz kolejny plik

Tutaj Twoja praca się opłaca, możesz to robić w kółko.

Wyciągnij kod z www/js/app.jsodwołań do $ i App.

na przykład

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

Włóż to www/js/foo.js

Na samej górze tego pliku umieść:

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

Na dole umieść:

})

Następnie zmień ostatnią linię www / js / main.js na:

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

Otóż ​​to! Zrób to za każdym razem, gdy chcesz umieścić kod we własnym pliku!


Ma to wiele problemów - oczywisty polega na tym, że na końcu fragmentujesz wszystkie pliki i wymuszasz 400 bajtów zmarnowanych danych każdemu użytkownikowi na skrypt na ładowanie strony , nie używając r.jspreprocesora. Co więcej, tak naprawdę nie rozwiązałeś problemu PO - przekazałeś tylko ogólne require.jsinstrukcje.
Sébastien Renauld

7
Co? Moja odpowiedź jest specyficzna dla tego pytania. Kolejnym krokiem jest oczywiście r.js, ale problemem jest organizacja, a nie optymalizacja.
Lyn Headley

Podoba mi się ta odpowiedź, nigdy nie korzystałem z require.js, więc będę musiał sprawdzić, czy mogę z niego skorzystać i odnieść z tego jakąkolwiek korzyść. Często używam wzorca modułu, ale może to pozwoli mi wyabstrahować pewne rzeczy, a następnie wymagać ich wprowadzenia.
Tony,

1
@ SébastienRenauld: ta odpowiedź nie dotyczy tylko require.js. Mówi głównie o posiadaniu przestrzeni nazw dla tworzonego kodu. Myślę, że powinieneś docenić dobre części i dokonać edycji, w której znajdziesz jakiekolwiek problemy. :)
mithunsatheesh

10

Jeśli chodzi o twoje pytanie i komentarze, zakładam, że nie chcesz przenosić swojego kodu do frameworka takiego jak Backbone, ani używać biblioteki ładującej, takiej jak Require. Chcesz po prostu lepszego sposobu zorganizowania kodu, który już masz, w najprostszy możliwy sposób.

Rozumiem, że irytujące jest przewijanie ponad 2000 wierszy kodu w celu znalezienia sekcji, nad którą chcesz popracować. Rozwiązaniem jest podzielenie kodu na różne pliki, po jednym dla każdej funkcji. Na przykład sidebar.js, canvas.jsitd. Następnie można połączyć je ze sobą do produkcji za pomocą Grunt wraz z Usemin można mieć coś takiego:

W Twoim html:

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

W Twoim Gruntfile:

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

Jeśli chcesz używać Yeoman, otrzymasz standardowy kod do tego wszystkiego.

Następnie dla każdego pliku musisz upewnić się, że postępujesz zgodnie z najlepszymi praktykami i że cały kod i zmienne znajdują się w tym pliku i nie są zależne od innych plików. Nie oznacza to, że nie można wywoływać funkcji jednego pliku z drugiego, chodzi o to, aby zmienne i funkcje były hermetyzowane. Coś podobnego do przestrzeni nazw. Zakładam, że nie chcesz przenosić całego kodu, aby był zorientowany obiektowo, ale jeśli nie masz nic przeciwko nieco refaktoryzacji, polecam dodanie czegoś równoważnego z tak zwanym wzorcem modułu. Wygląda mniej więcej tak:

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

Następnie możesz załadować ten fragment kodu w następujący sposób:

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

Pozwoli ci to mieć znacznie łatwiejszy w utrzymaniu kod bez konieczności zbytniego przepisywania kodu.


1
Możesz poważnie przemyśleć ten przedostatni fragment kodu, który nie jest lepszy niż posiadanie kodu wpisanego w tekście: wymaga tego Twój moduł #sidebar #sortable. Równie dobrze możesz zaoszczędzić sobie pamięci, po prostu wstawiając kod i zapisując dwa IETF.
Sébastien Renauld

Chodzi o to, że możesz użyć dowolnego kodu, którego potrzebujesz. Używam tylko przykładu z oryginalnego kodu
Jesús Carrera

Zgadzam się z Jezusem, to tylko przykład, OP może łatwo dodać opcję „obiekt”, która pozwoliłaby im określić selektor elementu zamiast na sztywno go zakodować, ale to był tylko szybki przykład. Chciałbym powiedzieć, że podoba mi się wzorzec modułu, jest to podstawowy wzorzec, którego używam, ale nawet jeśli to powiedziawszy, wciąż staram się lepiej zorganizować swój kod. Zwykle używam języka C #, więc nazewnictwo i tworzenie funkcji jest tak ogólne. Staram się zachować „wzorzec”, tak jak podkreślenia są lokalne i prywatne, zmienne są po prostu „obiektami”, a następnie odnoszę się do funkcji, która jest publiczna.
Tony

Jednak wciąż napotykam wyzwania związane z tym podejściem i pragnę znaleźć lepszy sposób na zrobienie tego. Ale działa o wiele lepiej niż po prostu deklarowanie moich zmiennych i funkcji w globalnej przestrzeni, aby powodować konflikty z innymi js .... lol
Tony

6

Użyj javascript MVC Framework, aby uporządkować kod javascript w standardowy sposób.

Najlepsze dostępne frameworki JavaScript MVC to:

Wybór frameworka JavaScript MVC wymagał uwzględnienia wielu czynników. Przeczytaj następujący artykuł porównawczy, który pomoże Ci wybrać najlepsze ramy na podstawie czynników ważnych dla Twojego projektu: http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

Możesz również użyć RequireJS z frameworkiem do obsługi ładowania plików i modułów Asynchrounous js.
Spójrz poniżej, aby rozpocząć ładowanie modułu JS:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/


4

Kategoryzuj swój kod. Ta metoda bardzo mi pomaga i działa z każdym frameworkiem 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();
    });
})();

W preferowanym edytorze (najlepszym jest Komodo Edit) możesz złożyć, zwijając wszystkie wpisy, a zobaczysz tylko tytuły:

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

2
+1 dla standardowego rozwiązania JS, które nie opiera się na bibliotekach.
hobberwickey

-1 z wielu powodów. Twój odpowiednik w kodzie jest dokładnie taki sam jak w OP ... + jeden IETF na „sekcję”. Ponadto używasz zbyt szerokich selektorów, nie pozwalając programistom modułów na zastąpienie ich tworzenia / usuwania lub rozszerzanie zachowania. Wreszcie IETF nie są darmowe.
Sébastien Renauld

@hobberwickey: Nie wiem o tobie, ale wolałbym polegać na czymś, co jest kierowane przez społeczność i gdzie błędy zostaną szybko znalezione, jeśli mogę. Zwłaszcza jeśli zrobienie czegoś innego skazuje mnie na ponowne wynalezienie koła.
Sébastien Renauld

2
Wszystko to polega na organizowaniu kodu w oddzielne sekcje. Ostatnim razem, gdy sprawdzałem, było to A: dobra praktyka i B: nie jest to coś, do czego naprawdę potrzebujesz biblioteki wspieranej przez społeczność. Nie wszystkie projekty pasują do Backbone, Angular itp., A modularyzacja kodu poprzez zawijanie go w funkcje jest dobrym rozwiązaniem ogólnym.
hobberwickey

To podejście jest możliwe, gdy zechcesz polegać na dowolnej ulubionej bibliotece. Ale powyższe rozwiązanie działa z czystym javascriptem, niestandardowymi bibliotekami lub dowolnym znanym frameworkiem js

3

Sugerowałbym:

  1. wzorzec wydawcy / subskrybenta do zarządzania zdarzeniami.
  2. orientacja obiektu
  3. przestrzeń nazw

W twoim przypadku Jessica podziel interfejs na strony lub ekrany. Strony lub ekrany mogą być obiektami i rozszerzyć je z niektórych klas nadrzędnych. Zarządzaj interakcjami między stronami za pomocą klasy PageManager.


Czy możesz rozwinąć ten temat za pomocą przykładów / zasobów?
Kivylius

1
Co rozumiesz przez „orientację obiektu”? Prawie wszystko w JS jest obiektem. W JS nie ma zajęć.
Bergi

2

Sugeruję użycie czegoś takiego jak Backbone. Backbone to biblioteka javascript obsługiwana przez RESTFUL. Ik sprawia, że ​​twój kod jest czystszy i bardziej czytelny, a także jest potężny, gdy jest używany razem z requirejs.

http://backbonejs.org/

http://requirejs.org/

Backbone nie jest prawdziwą biblioteką. Ma na celu nadanie struktury kodowi javascript. Może zawierać inne biblioteki, takie jak jquery, jquery-ui, google-maps itp. Backbone jest moim zdaniem najbliższym javascriptowym podejściem do struktur Object Oriented i Model View Controller.

Także jeśli chodzi o przepływ pracy. Jeśli budujesz swoje aplikacje w PHP, korzystaj z biblioteki Laravel. Będzie działać bezbłędnie z Backbone, gdy będzie używany z zasadą RESTfull. Poniżej link do Laravel Framework i tutorial o budowaniu RESTfull API:

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

http://laravel.com/

Poniżej znajduje się samouczek z nettuts. Nettuts ma wiele samouczków wysokiej jakości:

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


0

Może nadszedł czas, abyś zaczął wdrażać cały proces tworzenia oprogramowania przy użyciu takich narzędzi, jak yeoman http://yeoman.io/ . Pomoże to kontrolować wszystkie zależności, proces kompilacji i, jeśli chcesz, automatyczne testowanie. Na początek jest to dużo pracy, ale po wdrożeniu zmiany w przyszłości będą znacznie łatwiejsze.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.