Boas práticas com JavaScript e jQuery: código não-obstrusivo

Postado em 04. jun, 2012 por Gabriel Oliveira em JavaScript, Web Design

Já há bastante tempo, por conta do amadurecimento da Web como plataforma de aplicações, a programação front-end de aplicações para Web vem adotando alguns padrões e boas práticas. Os benefícios disso são claros, principalmente quando consideramos o fato de que até as mais simples páginas Web não são tão triviais: são compostas de camadas distintas de marcação, apresentação e interatividade, normalmente tendo HTML, CSS e JavaScript como responsáveis.

Uma das práticas mais importantes quando pensamos na tríade do front-end é o desacoplamento dessas camadas. Ou seja, não devemos adicionar informações visuais, nem sobre interatividade com o usuário, na camada de marcação. Dessa maneira, a manutenção de cada componente é muito mais fácil pois cada um tem seu lugar distinto. Infelizmente, a mistura ainda ocorre, como este frequente código:

<a style="font-size:16px" href="#" onclick="adicionaItem()">Adicionar item</a>

No exemplo acima, posso distinguir claramente quem não pertence ao HTML, se levarmos em consideração as camadas visuais (style) e de interatividade (onclick). Elas não deveriam estar ali. Para mantermos nossa marcação limpa, podemos utilizar um seletor do CSS para externalizar as informações visuais. Para deixar o exemplo simples, vamos trocar os atributos alienígenas (pertencentes a outras camadas) por um atributo id:

<a id="additem" href="#">Adicionar item</a>

Agora, podemos criar o seguinte seletor no CSS e adicioná-lo ao HTML de maneira adequada (tag <style> ou <link> para arquivo CSS externo):

#additem {
  font-size: 16px;
}

De acordo com o demonstrado no primeiro exemplo, o navegador deve disparar a execução da função JavaScript adicionaItem(), quando o usuário clicar na área da página ocupada pelo elemento <a>. A implementação dessa função não é de nosso interesse no exemplo, só precisamos executá-la. Como fazer isso sem adicionar essa função diretamente na marcação HTML?

Uma das características mais importantes da execução de JavaScript em uma página Web é que o navegador disponibiliza acesso a todo e qualquer elemento declarado no HTML através da DOM API (Document Object Model API). Isso significa que podemos, em nosso código JavaScript, criar objetos que fazem referência direta a tags do HTML.

Alguns desses objetos refletem a alteração de seus atributos imediatamente no navegador. Para implementar o comportamento necessário, devemos informar ao navegador que, em determinado elemento, há uma função a ser executada caso ele seja o taget de um evento. Para obtermos esse resutado, precisamos interagir com o EventListener desse elemento. Em JavaScript puro, teríamos a seguinte abordagem:

// Primeiro é necessário criar um objeto que faz
// referência ao elemento no HTML:
var linkAddItem = document.getElementById('additem');

// Depois adicionamos a função "adicionaItem" à lista de
// funções que devem ser executadas quando o usuário clica
// na área do elemento no navegador:
linkAddItem.addEventListener('click', adicionaItem, false);

Esse padrão é o que chamamos de JavaScript não-obstrusivo (não intrusivo). Estamos adicionando interatividade à página através do JavaScript, sem a adição de atributos e informações desnecessárias na marcação. Note que passamos como segundo argumento o nome da função somente, sem os parênteses, necessários para executá-la. Isso porque não queremos executá-la de fato, apenas delegar sua execução à ocorrência do evento.

Fato é que os navegadores (principalmente o IE antigo) não são exatamente consistentes na implementação desses objetos em suas APIs. A própria função addEventListener não existe no IE até sua versão 9. No IE8 e anteriores, devemos chamar a função attachEvent, que implementa o mesmo comportamento.

Para termos um código compatível entre navegadores, seria necessário realizar uma série de verificações para identificar qual abordagem tomar em cada caso. A boa notícia é que algumas bibliotecas de JavaScript fazem isso para nós. Elas abstraem essas diferenças em funções relativamente mais simples e compatíveis com os principais navegadores, versões e plataformas, se não todas.

Eventos com jQuery

O jQuery, hoje a biblioteca JavaScript que é quase onipresente, interage com os EventListeners de maneira bem direta:

$('#additem').on('click', adicionaItem);

Caso você precise usar uma versão anterior a 1.7 do jQuery, substitua a função “on()” pela “bind()”.

No código acima, a função $('#additem') nos retorna um objeto que representa o elemento com id additem em nossa marcação. Esse é um objeto do jQuery e podemos chamar sua função on() que precisa de dois argumentos: o nome do evento a ser observado e o nome da função que deve ser executada ao ocorrer esse evento na área que o elemento ocupa na página. Essa abordagem nos permite descartar o atributo alienígena onclick no HTML, sendo que o próprio JavaScript identifica qual função deve ser executada quando ocorre um evento em determinado elemento da página.

Nos casos em que a função a ser executada precise de argumentos, não podemos passar os argumentos com esse padrão, é necessário passar como argumento para a função on() uma função anônima e, dentro dessa, chamar a função adicionaItem() com argumentos.

Essa função anônima (bem como a função adicionaItem nos exemplos anteriores) recebe, por padrão, um objeto que representa o evento ocorrido no caso de sua execução. Esse objeto contém diversas informações interessantes como um timestamp de quando ocorreu o evento, no caso do evento click, quais eram as coordenadas do mouse na janela do navegador no momento do clique entre outras.

Vamos supor um número como argumento por exemplo:

$('#additem').on('click', function(event) {
    // Essa função anônima pode conter uma lógica mais complexa.
    adicionaItem(1);
});

Por estarmos adicionando um evento a um link nesse exemplo, o comportamento padrão do navegador é, após a execução da função JavaScript, o evento retornar ao seu ciclo normal e o usuário ser levado ao endereço declarado no link, nesse caso o topo da página (href="#"). Normalmente, esse comportamento é indesejado, então vamos informar nossa função que queremos anular o restante do ciclo do evento:

$('#additem').on('click', function(event) {
    // Essa função anônima pode conter uma lógica mais complexa.
    adicionaItem(1);

    // Anular a continuação do ciclo do evento no navegador:
    event.preventDefault();
});

Nos primeiros exemplos, quando passamos o nome da função diretamente à funcão on(), seria necessário adicionar a linha event.preventDefault() dentro da função adicionaItem() para obtermos o comportamento correto. Alguns desenvolvedores também utilizam return false; no lugar de event.preventDefault(); o que, em nosso simples exemplo, terá o mesmo resultado.

Esse tratamento direto do objeto event permite inclusive a adoção de um outro padrão recomendado: nenhum link deve levar ao destino “#” (topo da página), a não ser que seja essa sua finalidade. O ideal é que no atributo href seja utilizado um URL que realizará o mesmo comportamento que esperamos com o JavaScript, só que sem JavaScript. Esse fallback é importante para acessibilidade e atende dispositivos que não suportam JavaScript.

Podemos atribuir uma função ao EventListener de qualquer elemento, não só dos links. Nesses casos, não precisamos anular o comportamento padrão do evento com “event.preventDefault()” visto que clicar em uma <img> ou <div> qualquer não tem efeito colateral na interação do usuário com a página.

Esses e outros assuntos avançados de JavaScript, a gente discute em mais detalhes na Formação Web da Caelum.

Gabriel Oliveira ()

Tags: , ,

33 Respostas para “Boas práticas com JavaScript e jQuery: código não-obstrusivo”

  1. David

    04. jun, 2012

    A palavra obstrusivo não existe em português… Se você quer uma tradução para non-obstrusive, use não-intrusivo.

  2. Leonardo Lima

    04. jun, 2012

    Muito boa essa dica, a galera tem que se ligar que da para fazer várias coisas com javascript e jquery, valeu.

  3. Paulo Silveira

    04. jun, 2012

    oi David. assim como deletar, escanear, deletado, escaneado, di(s)ruptado, nao existiam. os neologismos aparecem conforme a língua evolui, e um adjetivação de um verbo é uma prática mais que aceita por qualquer gramático. ObstruTivo, obstruente, obstruído mostram isso. Mas sem dúvida é apenas um detalhe para o post. Talvez obstruTivo seja o mais correto.

  4. Gabriel Oliveira

    04. jun, 2012

    David, obrigado pela sugestão, na verdade o termo mais similar ao “obstrusive” no português seria “protuberante”, no caso algo que está lá chamando a atenção fora do lugar adequado.

    Em nossa área é muito comum a adoção de neologismo por cognação realmente para evitar traduções que levem à compreensão indevida do termo. Isso também facilita relação com material no idioma original da maioria do material disponível.

  5. Thiago

    04. jun, 2012

    Na verdade nem existe o termo “obstrusivo”, sempre usaram o termo javascript não-obstruTivo(se for verificar o significado da palavra, é o correto).

    Se a questão e “o que é mais usado?”: vários “estrelinhas” da nossa triste comunidade de devs brasileira também usam o termo obstrutivo, olha:
    http://akitaonrails.com/2010/05/10/rails-3-introducao-a-javascript-nao-obstrusivo-e-responders

    Acho que deviam mudar o título.

  6. Rodrigo

    04. jun, 2012

    Post muito bom, principalmente para ajudar entender na prática código não-obstrusivo. Parabéns e obrigado pela contribuição.

  7. Paulo Silveira

    04. jun, 2012

    Exato, me parece obstrutivo melhor, como comentei anteriormente.

    Thiago, o link do Akita que voce mandou caiu no mesmo caso. Repare que no permlink ele usou obstruSivo e depois passou para obstruTivo :) .

    E também acho que demonstra que a comunidade não é triste, pelo contrário, produzir bastante em português é um sinal positivo. Triste é quando a gente diminui o valor de quem é mais conhecido. Vira ad hominem. Parece que ser conhecido e famoso é negativo. Uma inversão de valores *bem* estranha.

  8. Roberto Souza

    04. jun, 2012

    Parabéns! Boa introdução para quem ainda faz aquele macarrão em JavaScript (tem muita gente, inclusive eu, kkkk).

    E pensar que tem gente que vem aqui só para comentar como qual melhor forma de traduzir tal termo, kkkk.

  9. Paulo Silveira

    04. jun, 2012

    Coloquei alguns links para traducoes que poderiam ser. Fui ver e Gabriel parece ter razao: o codigo nao esta obstruindo nada. Não impede, apenas é importuno.

  10. Fábio Miranda

    04. jun, 2012

    Mto bom post. Valeria a pena também mencionar os Namespaced Events: http://docs.jquery.com/Namespaced_Events

    Melhora a organização dos event handlers e permite usar unbind/off de forma seletiva quando necessário remover algum event handler.

  11. Gabriel Oliveira

    04. jun, 2012

    Obrigado Fábio, planejamos deixar o “off” (unbind), assim como delegate, para próximos posts de uma série sobre boas práticas de JavaScript e jQuery.

  12. Caio Ribeiro Pereira

    04. jun, 2012

    Muito show esse post! Parabéns!

  13. Tiago Ribeiro

    04. jun, 2012

    Parabens Gabriel pelo ótimo post!

  14. Rafael Mueller

    05. jun, 2012

    Legal o post,

    Apenas uma correção sobre o event.preventDefault().

    Na verdade “return false;” não terá o mesmo comportamento que o event.preventDefault().

    return false interrompe o bubbling do evento, diferente do preventDefault().

    Coloquei um exemplo em http://jsfiddle.net/3h87g/ para mostrar a diferença.

    O bubbling de evento é muito bem utilizado quando você precisa colocar o mesmo evento em vários elementos do DOM, é bem mais perfomático quando você pode adicionar um handler para o evento em um elemento pai, ao invés de colocar em todos filhos.

  15. Rafael Mueller

    05. jun, 2012

    Apenas complementando…

    return false() terá o mesmo comportamento que
    event.preventDefault();
    event.stopPropagation();

  16. Gabriel Oliveira

    05. jun, 2012

    Obrigado Rafael, realmente faltou citar que o uso do “return false” teria o mesmo “efeito prático no caso do nosso exemplo”, em algumas situações mais complexas precisamos avaliar o uso correto de preventDefault e stopPropagation.

  17. Gabriel Oliveira

    05. jun, 2012

    Ah, Rafael, e quanto à delegar eventos ao handler do elemento pai, teremos um post em breve sobre esse padrão e quando é recomendado adotá-lo.

  18. Edinei

    06. jun, 2012

    Muito bom o post @Gabriel, só queria comentar a respeito de alguns possíveis “trade-off” e se puderem comentar a respeito.

    1 – Se todos listeners da minha página forem adicionados da forma como foi apresentado (programaticamente), tenho q navegar pela dom usando ou não algum framework como JQuery, ou seja essa query no DOM pode trazer um carregamento um pouco mais lento. Correto?

    2 – Esses mesmos listeners deveriam ser adicionados após a página ser renderizada (dom:loaded) pois há um risco de tentar adicionar um listener a um elemento e esse ainda nem ter sido renderizado.

    Ou seja realmente vale o esforço (no quesito JS) esse tipo de prática?? Obrigado e abs…

  19. Gabriel Oliveira

    06. jun, 2012

    Oi @Edinei, o único caso que acredito ser perceptível a performance menor dessa abordagem é se você tiver milhares de elementos em sua página e cada um precisar de uma função diferente em seu handler.

    Ou se você itera sobre uma coleção de milhares de elementos e passa uma função anônima ao handler de cada um (num bloco “for” por exemplo), daí o navegador cria milhares de funções idênticas. Por isso preferimos a abordagem de passar uma função existente ao handler.

    A busca no DOM não traz nenhuma penalidade de performance, inclusive é um dos pontos de performance que mais evoluiu nos navegadores ultimamente.

    Quanto a esperar o evento DOMContentLoaded para fazer isso, não é exatamente necessário, simplesmente colocar todos os scripts como últimos elementos dentro do body resolve (além de melhorar a performance da página como um todo).

    Se estiver usando jQuery deve estar familiarizado a colocar todos os códigos dentro de uma função anônima:

    $(function() { /* código */ });

    ou

    $(document).ready(function() { /* código */ });

    Isso garante que seu código vá rodar após o DOMContentLoaded, portanto é seguro usar esse padrão.

    Obrigado, abraço!

  20. Felipe Caparelli

    13. jun, 2012

    É sempre bom vermos as melhores práticas, eu mesmo utilizo tanto a maneira “incorreta” quanto a “correta”. Acho vantagem nas duas, mas concordo plenamente que colocar o atributo style direto nas tags fica ruim demais pra manutenção. Só que considero a abordagem de usar o atributo onclick muito mais legível para os iniciantes o que ajuda em alguns casos.

    Parabéns pelo post… o blog da Caelum está mesmo com um conteúdo bem diversificado e rico! Excelente!

  21. Otto

    13. jun, 2012

    Gostei do artigo. Só fiquei curioso para saber como organiza o javascript de forma a manter a legibilidade e otimização? Cria um único arquivo .js, coloca na mesma página… ?

  22. Bernardo Pires

    13. jun, 2012

    Acredito que a informacao no paragrafo abaixo esteja equivocada:

    “Nos casos em que a função a ser executada precise de argumentos, não podemos passar os argumentos com esse padrão, é necessário passar como argumento para a função on() uma função anônima e, dentro dessa, chamar a função adicionaItem() com argumentos.”

    De fato o jquery aceita como 2o parametro da funcao on() um handler qualquer, ou seja, qualquer funcao. Essa funcao sera chamada quando o evento for ativado. O fato de ser anonimo nao faz diferenca nenhuma.

  23. Gabriel Oliveira

    13. jun, 2012

    Oi Bernardo, o que quis dizer é o seguinte:

    Caso 1- A função adicionaItem não requer argumentos:

    $(‘#additem’).on(‘click’, adicionaItem);

    Aqui atribuímos a função adicionaItem() ao handler o elemento.

    Caso 2- A função adicionaItem requer um argumento:

    $(‘#additem’).on(‘click’, adicionaItem(‘argValue’));

    Aqui estamos atribuindo o RETORNO da função adicionaItem ao handler, se a função não retornar nada explicitamente, “undefined” será atribuído. Não é isso que queremos. Queremos isso:

    $(‘#additem’).on(‘click’, function(event) { adicionaItem(‘argValue’); });

    Estamos atribuindo uma função ao handler, e essa função executa adicionaItem com o atributo requerido.

  24. Rafael

    13. jun, 2012

    Galera muito bom parabéns!

  25. Moacir

    14. jun, 2012

    Parabéns pelo post! Está excelente! Muito útil e esclarecedor!

  26. Iderlando

    16. jun, 2012

    Muito bom o post, parabéns! E só voltando um pouco ao assunto do primeiro comentário, o adjetivo “obstrusivo” existe sim na língua portuguesa. Uma busca por essa palavra pode ser feito no VOLP, o Vocabulário Ortográfico da Língua Portuguesa no site da ABL. Segue o link http://www.academia.org.br/abl/cgi/cgilua.exe/sys/start.htm?sid=23

  27. Julio Cesar Cabral

    19. jun, 2012

    Gabriel,

    Fantástico o seu post. Encaminhei para todos aqui onde eu trabalho para lerem o artigo. Parabéns.

  28. Ivan

    05. jul, 2012

    Na boa, o post show desse o cara vem se preocupar com a existência ou não de um termo :/, tem gente q já nasce do contra.

  29. Thadeu esteves

    21. dez, 2012

    Muito bom o post, porém ja existem melhores praticas pra se evitar o jQuery , por exemplo , document.querySelector(‘.class’) que faz a mesma coisa que o $…

  30. Gabriel Oliveira

    21. dez, 2012

    Oi Thadeu, o maior problema em evitar o jQuery nesse caso não é bem a função que utilizamos para “pegar” um elemento no DOM, e sim o “addEventListener” que está presente no IE somente nas versões 9 e 10. Como o IE 8 ainda é bastante utilizado e ainda precisa ser suportado em muitos projeots, seria preciso fazer sempre uma verificação para sabermos se é necessário usar a função “attachEvent” ao invés da padrão “addEventListener”.

    Vale ressaltar também que a função “$” do jQuery faz muito mais do que simplesmente “pegar” o elemento no DOM…

  31. Andreas

    11. abr, 2013

    Parabéns pela iniciativa. Muito boa a explicação.

  32. Leandro

    03. mai, 2013

    Parabéns!

Trackbacks/Pingbacks

  1. JavaScript não-obstrusivo at SPasqualino - junho 5, 2012

    [...] Ótimo artigo sobre JavaScript não-obstrusivo: http://blog.caelum.com.br/boas-praticas-com-javascript-e-jquery-codigo-nao-obstrusivo/ [...]

Deixar uma Resposta