Melhor maneira de organizar o código jQuery / JavaScript (2013) [fechado]

104

O problema

Esta resposta já foi respondida antes, mas é antiga e desatualizada. Tenho mais de 2.000 linhas de código em um único arquivo e, como todos sabemos, essa é uma prática ruim, especialmente quando estou examinando o código ou adicionando novos recursos. Quero organizar melhor meu código, agora e no futuro.

Devo mencionar que estou construindo uma ferramenta (não um site simples) com muitos botões, elementos de interface do usuário, arrastar, soltar, ouvintes / manipuladores de ação e função no escopo global onde vários ouvintes podem usar a mesma função.

Código de exemplo

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

... // Around 75 more of this

function update_html(){ .... }

...

Mais código de exemplo

Conclusão

Eu realmente preciso organizar este código para melhor uso e não para me repetir e ser capaz de adicionar novos recursos e atualizar os antigos. Eu estarei trabalhando nisso sozinho. Alguns seletores podem ter 100 linhas de código, outros são 1. Eu olhei um pouco require.jse achei meio repetitivo e, na verdade, escrevendo mais código do que o necessário. Estou aberto a qualquer solução possível que se encaixe neste critério e link para recursos / exemplos são sempre uma vantagem.

Obrigado.

Kivylius
fonte
Se você quiser adicionar backbone.js e require.js, será muito trabalhoso.
jantimon
1
Que tarefas você se encontra realizando continuamente ao escrever isso?
Mike Samuel
4
Você já visitou codereview.stackexchange.com ?
Antônio
4
Aprenda Angular! É o futuro.
Onur Yıldırım
2
Seu código não deve estar em um link externo, mas aqui. Além disso, @codereview é um lugar melhor para esses tipos de perguntas.
George Stocker

Respostas:

98

Vou repassar algumas coisas simples que podem, ou não, ajudá-lo. Alguns podem ser óbvios, alguns podem ser extremamente misteriosos.

Etapa 1: compartimentar seu código

Separar seu código em várias unidades modulares é um primeiro passo muito bom. Reúna o que funciona "junto" e coloque-os em sua própria pequena unidade encerrada. não se preocupe com o formato por enquanto, mantenha-o embutido. A estrutura é um ponto posterior.

Então, suponha que você tenha uma página como esta:

insira a descrição da imagem aqui

Faria sentido compartimentar para que todos os manipuladores / fichários de eventos relacionados ao cabeçalho estivessem lá, para facilitar a manutenção (e não ter que peneirar 1000 linhas).

Você pode então usar uma ferramenta como o Grunt para reconstruir seu JS em uma única unidade.

Etapa 1a: gerenciamento de dependências

Use uma biblioteca como RequireJS ou CommonJS para implementar algo chamado AMD . O carregamento de módulo assíncrono permite declarar explicitamente do que seu código depende, o que permite descarregar a chamada de biblioteca para o código. Você pode apenas dizer literalmente "Isso precisa do jQuery" e o AMD irá carregá-lo e executar seu código quando o jQuery estiver disponível .

Isso também tem uma joia oculta: o carregamento da biblioteca será feito no segundo em que o DOM estiver pronto, não antes. Isso não interrompe mais o carregamento de sua página!

Etapa 2: modularizar

Veja o wireframe? Eu tenho dois blocos de anúncios. Provavelmente, eles terão ouvintes de eventos compartilhados.

Sua tarefa nesta etapa é identificar os pontos de repetição em seu código e tentar sintetizar tudo isso em módulos . Módulos, agora, vão abranger tudo. Vamos dividir as coisas à medida que avançamos.

A idéia geral desta etapa é ir da etapa 1 e excluir todas as massas de cópia, para substituí-las por unidades que estão fracamente acopladas. Então, em vez de ter:

ad_unit1.js

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

ad_unit2.js

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

Eu terei:

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

O que permite a você compartimentar entre seus eventos e sua marcação , além de se livrar da repetição. Este é um passo bastante decente e vamos estendê-lo mais tarde.

Etapa 3: Escolha uma estrutura!

Se você gostaria de modularizar e reduzir as repetições ainda mais, existem vários frameworks incríveis que implementam abordagens MVC (Model - View - Controller). Meu favorito é Backbone / Spine, porém, também tem Angular, Yii, ... A lista é longa.

Um modelo representa seus dados.

Uma visualização representa sua marcação e todos os eventos associados a ela

Um controlador representa sua lógica de negócios - em outras palavras, o controlador informa à página quais visualizações carregar e quais modelos usar.

Essa será uma etapa de aprendizado significativa, mas o prêmio vale a pena: ele favorece um código limpo e modular em vez de espaguete.

Existem muitas outras coisas que você pode fazer; são apenas diretrizes e ideias.

Mudanças específicas do código

Aqui estão algumas melhorias específicas em seu código:

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

            }

        }

    });
 });

Isso é melhor escrito como:

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

Anteriormente em seu código:

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

De repente, você tem uma maneira de criar uma camada padrão de qualquer lugar em seu código sem copiar e colar. Você está fazendo isso em cinco lugares diferentes. Acabei de salvar cinco cópias-pastas para você.

Mais um:

// Wrapper de conjunto de regras para ações

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

Esta é uma maneira muito potente de registrar regras se você tiver eventos que não sejam padrão ou eventos de criação. Isso também é muito difícil quando combinado com um sistema de notificação pub / sub e quando vinculado a um evento que você dispara sempre que cria elementos. Disparar e esquecer a associação de eventos modulares!

Sébastien Renauld
fonte
2
@Jessica: por que uma ferramenta online deveria ser diferente? A abordagem ainda é a mesma: compartimentalizar / modularizar, promover acoplamento fraco entre componentes usando uma estrutura (todos eles vêm com delegação de eventos atualmente), dividir seu código. O que não se aplica à sua ferramenta? O fato de você ter muitos botões?
Sébastien Renauld
2
@Jessica: Atualizado. Simplifiquei e agilizei a criação de camadas usando um conceito semelhante a a View. Assim. Como isso não se aplica ao seu código?
Sébastien Renauld
10
@Jessica: Dividir em arquivos sem otimizar é como comprar mais gavetas para guardar lixo. Um dia, você tem que esvaziar, e é mais fácil limpar antes que as gavetas se encherem. Por que não fazer os dois? Neste momento, parece que você vai querer um layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.jsse você está indo só para dividir o seu código-se. Use gruntpara reconstruí-los em um e Google Closure Compilerpara compilar e minimizar.
Sébastien Renauld
3
Ao usar o require.js, você deve realmente olhar para o otimizador r.js também, é isso que realmente faz o require.js valer a pena usar. Ele combinará e otimizará todos os seus arquivos: requirejs.org/docs/optimization.html
Willem D'Haeseleer
2
@ SébastienRenauld, sua resposta e comentários ainda são muito apreciados por outros usuários. Se isso pode fazer você se sentir melhor;)
Adrien Be
13

Esta é uma maneira simples de dividir sua base de código atual em vários arquivos, usando require.js. Vou mostrar como dividir seu código em dois arquivos. Adicionar mais arquivos será simples depois disso.

Etapa 1) No topo do seu código, crie um objeto App (ou qualquer nome de sua preferência, como MyGame):

var App = {}

Etapa 2) Converta todas as suas variáveis ​​e funções de nível superior para pertencer ao objeto App.

Ao invés de:

var selected_layer = "";

Você quer:

App.selected_layer = "";

Ao invés de:

function getModified(){
...
}

Você quer:

App.getModified = function() {

}

Observe que, neste ponto, seu código não funcionará até que você conclua a próxima etapa.

Etapa 3) Converter todas as referências de variáveis ​​e funções globais para passar pelo aplicativo.

Mudar coisas como:

selected_layer = "."+classes[1];

para:

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

e:

getModified()

para:

App.GetModified()

Etapa 4) Teste seu código neste estágio - tudo deve funcionar. Provavelmente, você obterá alguns erros no início porque perdeu algo, então corrija-os antes de prosseguir.

Etapa 5) Configure os requirejs. Presumo que você tenha uma página da web, servida por um servidor da web, cujo código está em:

www/page.html

e jquery em

www/js/jquery.js

Se esses caminhos não forem exatamente assim, o seguinte não funcionará e você terá que modificar os caminhos.

Faça download de requirejs e coloque require.js em seu www/jsdiretório.

em seu page.html, exclua todas as tags de script e insira uma tag de script como:

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

criar www/js/main.jscom conteúdo:

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

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

em seguida, coloque todo o código que você acabou de corrigir nas etapas 1-3 (cuja única variável global deve ser App) em:

www/js/app.js

No início desse arquivo, coloque:

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

Na parte inferior, coloque:

})

Em seguida, carregue page.html em seu navegador. Seu aplicativo deve funcionar!

Etapa 6) Crie outro arquivo

É aqui que seu trabalho compensa, você pode fazer isso indefinidamente.

Retire algum código das www/js/app.jsreferências $ e App.

por exemplo

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

Coloque dentro www/js/foo.js

No início desse arquivo, coloque:

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

Na parte inferior, coloque:

})

Em seguida, altere a última linha de www / js / main.js para:

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

É isso aí! Faça isso toda vez que quiser colocar o código em seu próprio arquivo!

Lyn Headley
fonte
Isso tem vários problemas - o mais óbvio é que você está fragmentando todos os seus arquivos no final e forçando 400 bytes de dados desperdiçados para cada usuário por script por carregamento de página ao não usar o r.jspré - processador. Além disso, você ainda não abordou o problema do OP - apenas forneceu um require.jstutorial genérico .
Sébastien Renauld
7
Hã? Minha resposta é específica para esta pergunta. E r.js é obviamente o próximo passo, mas o problema aqui é organização, não otimização.
Lyn Headley
Gostei da resposta, nunca usei o require.js, então terei que ver se posso usá-lo e obter algum benefício com isso. Eu uso o padrão de módulo muito, mas talvez isso me permita abstrair algumas coisas e, em seguida, exigir que sejam inseridas.
Tony
1
@ SébastienRenauld: essa resposta não é apenas sobre require.js. Ele fala principalmente sobre ter um namespace para o código que você está construindo. Eu acho que você deveria apreciar as partes boas e fazer uma edição para encontrar algum problema com isso. :)
mithunsatheesh
10

Para suas perguntas e comentários, assumirei que você não deseja portar seu código para uma estrutura como o Backbone ou usar uma biblioteca de carregador como Require. Você só quer uma maneira melhor de orgainze o código que já possui, da maneira mais simples possível.

Eu entendo que é irritante rolar por mais de 2.000 linhas de código para encontrar a seção na qual você deseja trabalhar. A solução é dividir seu código em arquivos diferentes, um para cada funcionalidade. Por exemplo sidebar.js, canvas.jsetc. Então você pode juntá-los para produção usando o Grunt, junto com o Usemin você pode ter algo assim:

Em seu html:

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

Em seu Gruntfile:

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

Se você quiser usar o Yeoman, ele lhe dará um código clichê para tudo isso.

Então, para cada arquivo em si, você precisa se certificar de que segue as práticas recomendadas e que todo o código e variáveis ​​estão todos nesse arquivo e não dependem de outros arquivos. Isso não significa que você não pode chamar funções de um arquivo a partir de outro, o ponto é ter variáveis ​​e funções encapsuladas. Algo semelhante a namespacing. Assumirei que você não deseja portar todo o seu código para orientação a objetos, mas se não se importar em refatorar um pouco, recomendo adicionar algo equivalente ao que é chamado de padrão de módulo. É mais ou menos assim:

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

Então você pode carregar este trecho de código como este:

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

Isso permitirá que você tenha um código muito mais sustentável sem ter que reescrever muito o código.

Jesús Carrera
fonte
1
Você pode querer reconsiderar seriamente aquele penúltimo trecho, que não é melhor do que ter o código escrito embutido: seu módulo requer #sidebar #sortable. Você também pode economizar memória inserindo o código e salvando os dois IETFs.
Sébastien Renauld
A questão é que você pode usar qualquer código de que precisar. Estou apenas usando um exemplo do código original
Jesús Carrera
Eu concordo com Jesus, este é apenas um exemplo, o OP pode facilmente adicionar um "objeto" de opções que permitiria a eles especificar o seletor do elemento em vez de codificá-lo, mas este foi apenas um exemplo rápido. Gostaria de dizer que adoro o padrão de módulo, é o padrão principal que uso, mas mesmo com isso dito, ainda estou tentando organizar melhor meu código. Eu uso C # normalmente, então a nomenclatura e a criação de funções parecem tão genéricas. Eu tento manter um "padrão" como sublinhados são locais e privados, variáveis ​​são apenas "objetos" e, em seguida, faço referência à função em meu retorno que é público.
Tony de
No entanto, ainda encontro desafios com essa abordagem e desejo ter uma maneira melhor de fazer isso. Mas funciona muito melhor do que apenas declarar minhas variáveis ​​e funções no espaço global para causar conflitos com outros js .... lol
Tony
6

Use o javascript MVC Framework para organizar o código javascript de uma forma padrão.

As melhores estruturas JavaScript MVC disponíveis são:

A seleção de uma estrutura JavaScript MVC exigiu muitos fatores a serem considerados. Leia o seguinte artigo de comparação que o ajudará a selecionar a melhor estrutura com base nos fatores importantes para o seu projeto: http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

Você também pode usar RequireJS com a estrutura para suportar o carregamento de arquivo e módulo Asynchrounous js.
Veja abaixo para começar a carregar o módulo JS:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

Rohit Tailor
fonte
4

Categorize seu código. Este método está me ajudando muito e funciona com qualquer 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();
    });
})();

No seu editor preferido (o melhor é Komodo Edit), você pode dobrar recolhendo todas as entradas e verá apenas os títulos:

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

fonte
2
1 para uma solução JS padrão que não depende de bibliotecas.
hobberwickey
-1 por vários motivos. Seu equivalente de código é exatamente o mesmo que o OP's ... + um IETF por "seção". Além disso, você está usando seletores excessivamente amplos sem permitir que os desenvolvedores de módulo substituam a criação / remoção daqueles ou estendam o comportamento. Finalmente, os IETFs não são gratuitos.
Sébastien Renauld
@hobberwickey: Não sei sobre você, mas prefiro confiar em algo voltado para a comunidade e onde os bugs serão encontrados rapidamente, se possível. Especialmente se fazer o contrário me condenar a reinventar a roda.
Sébastien Renauld
2
Tudo o que isso está fazendo é organizar o código em seções discretas. A última vez que verifiquei era A: boa prática e B: não é algo para o qual você realmente precisa de uma biblioteca com suporte da comunidade. Nem todos os projetos se encaixam em Backbone, Angular, etc., e modularizar o código envolvendo-o em funções é uma boa solução geral.
hobberwickey
É possível, a qualquer momento, contar com qualquer biblioteca favorita para usar essa abordagem. Mas a solução acima funciona com javascript puro, bibliotecas personalizadas ou qualquer framework js famoso
3

Eu sugeriria:

  1. editor / assinante padrão para gerenciamento de eventos.
  2. orientação do objeto
  3. namespacing

No seu caso, Jessica, divida a interface em páginas ou telas. As páginas ou telas podem ser objetos e estendidas de algumas classes pai. Gerenciar as interações entre as páginas com uma classe PageManager.

Fazle Rabbi
fonte
Você pode expandir isso com exemplos / recursos?
Kivylius
1
O que você quer dizer com "orientação a objetos"? Quase tudo em JS é um objeto. E não há classes em JS.
Bergi
2

Eu sugiro que você use algo como Backbone. Backbone é uma biblioteca javascript com suporte RESTFUL. Ik torna seu código mais limpo e legível e é poderoso quando usado junto com requirejs.

http://backbonejs.org/

http://requirejs.org/

O Backbone não é uma biblioteca real. Destina-se a dar estrutura ao seu código javascript. É capaz de incluir outras bibliotecas como jquery, jquery-ui, google-maps, etc. O backbone é, na minha opinião, a abordagem javascript mais próxima das estruturas Orientada a Objetos e Controlador de Visualização de Modelo.

Também em relação ao seu fluxo de trabalho .. Se você constrói suas aplicações em PHP, use a biblioteca Laravel. Ele funcionará perfeitamente com o Backbone quando usado com o princípio RESTfull. Abaixo o link para o Laravel Framework e um tutorial sobre como construir APIs RESTfull:

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

http://laravel.com/

Abaixo está um tutorial de nettuts. Nettuts tem muitos tutoriais de alta qualidade:

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

Chris Visser
fonte
0

Talvez seja a hora de você começar a implementar todo um fluxo de trabalho de desenvolvimento usando ferramentas como yeoman http://yeoman.io/ . Isso ajudará a controlar todas as suas dependências, processo de construção e, se desejar, testes automatizados. É muito trabalhoso para começar, mas uma vez implementado, tornará as alterações futuras muito mais fáceis.

drobson
fonte