Guia rápido para um JavaScript quase avançado

Um passeio por algumas características OO

por
última atualização: 21 de fevereiro de 2006
traduzido por
em: novembro de 2008

Ei, eu não sabia que você podia fazer isso

Se você é um desenvolver web e veio do mesmo lugar que eu venho, você provavelmente usou mais que um pouco de JavaScript em suas páginas web, na maioria das vezes como "cola" na UI (interface do usuário).

Até recentemente, eu sabia que o JavaScript tinha mais capacidades OO que eu estava empregando, mas eu não sentia que eu precisava utilizar isso. Como os browsers começaram a suportar um JavaScript e DOM mais padronizado, isso tornou viável escrever código mais complexo e funcional para rodar no cliente. Isso ajudou dando inicio ao fenômeno AJAX.

Como todos nós começamos a aprender como poderíamos escrever nossas aplicações "cool" AJAX, nós começamos a notar que o JavaScript que estávamos acostumados a conhecer era apenas a ponta do iceberg. Nós agora vemos Javascript sendo utilizado além de coisas simples na UI, como validação de dados e tarefas frívolas. O código do lado cliente agora está muito mais avançado e composto, muito mais como um verdadeiro aplicativo desktop ou um cliente robusto de uma aplicação cliente-servidor. Nós vemos bibliotecas de classe, modelos de objeto, hierarquias, padrões (patterns) e muitas outras coisas que nós estávamos acostumados a ver apenas no código do lado servidor.

De muitas formas nós podemos dizer que repentinamente a barra foi colocada num nível muito mais alto que antes. Isso torna necessária uma maior proficiência para escrever aplicativos para a nova WEB e nós precisamos aprimorar nossas habilidades com Javascript para chegar lá. Se você tentar utilizar alguma das muitas bibliotecas Javascript existentes atualmente, como Prototype.js, Scriptaculous, moo.fx, Behaviour, YUI, etc. você irá eventualmente ver você mesmo lendo código JS. Talvez porque você deseje aprender como eles fazem isso ou talvez apenas por curiosidade ou mais freqüentemente porque essa é a única forma de descobrir como utilizá-las, já que a documentação não parece ser muito completa na maioria dessas bibliotecas. Não importa qual a sua razão, você irá se deparar com técnicas "kung-fu" que serão assustadoras e desconhecidas se você não tiver visto nada igual antes.

O propósito desse artigo é precisamente explicar os tipos de construção que muitos de nós ainda não temos familiaridade. 

Artigo relacionado

Documentaçãodo Prototype.js

JSON

JavaScript Object Notation (JSON) é uma das novas "buzzwords" rodeando sobre o termo AJAX. JSON, colocando de forma simples, é um jeito de declarar um objeto em javascript. Vamos ver um exemplo agora mesmo e ver como isso é simples.


var myPet = { color: 'black', leg_count: 4, communicate: function(repeatCount){ 
for(i=0;i<repeatCount;i++) alert('Woof!');} };
   

Vamos apenas adicionar um pouco de formatação e isso irá se parecer mais com o que normalmente encontramos por aí:


var myPet = {
 color: 'black', 
 legCount: 4, 
 communicate: function(repeatCount){
  for(i=0;i<repeatCount;i++)
   alert('Woof!');
 } 
};
   

Aqui nós criamos uma referencia para um objeto com duas propriedades ((color e (legCount ) e um método ( communicate ). Não é muito difícil descobrir que as propriedades e métodos são definidos em uma lista delimitada por vírgula. Cada um dos membros é introduzido por um nome, seguido por dois pontos e a seguir sua definição. No caso das propriedades isso é fácil, apenas o valor da propriedade. Os métodos são criados associando uma função anônima, a qual iremos explicar melhor mais abaixo. Após o objeto ser criado e atribuído para a variável myPet, nós podemos utilizá-lo assim:

alert('meu pet é ' + myPet.color);
alert('meu pet tem ' + myPet.legCount + ' pernas');
//se você é um cachorro lata 3 vezes:
myPet.communicate(3);
   

Nós veremos JSON sendo utilizado bastante por aí em Javascript nos dias atuais, como argumentos para funções, como retorno de funções, como retorno de chamadas no servidor (em strings), etc.

O que você quer dizer ? Uma função também é um objeto ?

Isso pode soar bastante estranho para desenvolvedores que nunca pensaram sobre isso, mas em JS uma função também é um objeto. Você pode passar uma função como um argumento para outra função da mesma forma que você passa uma string, por exemplo. Isso é utilizado extensivamente e é muito usual.

Dê uma olhada nesse exemplo. Nós iremos passar funções para outra função que irá utilizá-las.


var myDog = {
 bark: function(){
  alert('Woof!');
 }
};

var myCat = {
 meow: function(){
  alert('Eu sou um gato preguiçoso. Eu não vou miar (meow) para você.');
 }
};
 
function annoyThePet(petFunction){
 //Vamos ver o que o nosso animal de estimação pode fazer 
 petFunction();
}

//aborreça o cachorro:
annoyThePet(myDog.bark);
//aborreça o gato:
annoyThePet(myCat.meow);
   

Note que nós passamos myDog.bark e myCat.meow sem os parênteses () anexados a eles. Se nós fizéssemos isso nós não estaríamos passando a função e sim chamando o método e passando o valor retornado, que seria indefinido undefined nesses dois casos.

Se você desejar fazer meu gato preguiçoso começar a latir, você poderia facilmente fazer isso:


myCat.meow = myDog.bark;
myCat.meow(); //alerts 'Woof!'
   

Arrays, itens, e membros de object

As seguintes linhas em JS fazem a mesma coisa.


var a = new Array();
var b = [];
   

Com eu tenho certeza que você já sabe, você pode acessar os itens individualmente em um array utilizando colchetes:


var a = ['primeiro', 'segundo', 'terceiro'];
var v1 = a[0];
var v2 = a[1];
var v3 = a[2];
   

Mas você não está limitado aos índices numéricos. Você pode acessar qualquer membro de um objeto JS utilizando o seu nome, em uma string. O exemplo seguinte cria um objeto vazio e adiciona alguns membros por nome.


var obj = {}; // novo, objeto vazio
obj['member_1'] = 'esse é o valor membro';
obj['flag_2'] = false;
obj['some_function'] = function(){ /* faça algo */};
   

O código acima tem um efeito idêntico ao que segue : 


var obj = {
 member_1:'esse é o valor membro',
 flag_2: false,
 some_function: function(){ /* faça algo */}
};
   

Em muitas formas, a idéia de objetos e arrays associativos (hashes) em JS não são distinguíveis. As duas linhas seguintes fazem a mesma coisa também.


obj.some_function();
obj['some_function']();
   

Já temos o suficiente sobre objetos, posso ter uma classe agora ?

O grande poder das linguagens orientadas a objeto deriva do uso de classes. Eu não acredito que eu teria descoberto como classes são definidas em JS utilizando apenas minha experiência prévia com outras linguagens. Julgue por você mesmo.


//defining a new class called Pet
var Pet = function(petName, age){
 this.name = petName;
 this.age = age;
};

//vamos criar um objeto da classe Pet
var famousDog = new Pet('Santa\'s Little Helper', 15);
alert('Esse pet se chama ' + famousDog.name);
   

Vamos ver como nós adicionamos um método a nossa classe Pet. Nós iremos utilizar a propriedade prototype que todas as classes possuem. A propriedade prototype é um objeto que contém todos os membros de qualquer objeto que a classe irá ter. Até mesmo as classes padrão do JS, como String, Number e Date possuem um objeto prototype no qual nós podemos adicionar métodos e propriedades e dessa forma fazer com que todo e qualquer objeto dessa classe automaticamente ganhe um novo membro.


Pet.prototype.communicate = function(){ 
 alert('Eu não sei o que eu deveria dizer, mas meu nome é ' + this.name);
};
   

Nessa hora que uma biblioteca com prototype.js vem a calhar. Se nós utilizarmos a prototype.js, nós podemos fazer nosso código parecer mais limpo (pelo menos em minha opinião) 


var Pet = Class.create();
Pet.prototype = {
 //nosso 'construtor'
 initialize: function(petName, age){
  this.name = petName;
  this.age = age;
 },
 
 communicate: function(){
  alert('Eu não sei o que eu deveria dizer, mas meu nome é ' + this.name);
 }
}; 
   

Funções como argumentos, um padrão interessante

Se você nunca trabalhou com linguagens que suportem "closures", como Ruby ou C# 2.0, você talvez ache o seguinte dialeto muito assustador. 


var myArray = ['primeiro', 'segundo', 'terceiro'];
myArray.each( function(item, index){
 alert('O item na posição #' + index + ' é:' + item);
});
   

Uau! Vamos explicar o que está acontecendo aqui antes que você decida que eu fui muito longe e navegue para outro artigo melhor que esse.

Primeiro, no exemplo acima nós estamos utilizando a biblioteca prototype.js, que adiciona a função "each" à classe Array. A função "each" aceita um argumento que é uma função objeto. Essa função, por sua vez, será chamada uma vez para cada item do array, passando dois argumentos quando for chamada, o item e o índice do item corrente. Vamos chamar essa função de nossa função de iteração (iterator function). Nós também podemos escrever o código da seguinte forma:


function myIterator(item, index){
 alert('O item na posição #' + index + ' é:' + item);
}

var myArray = ['primeiro', 'segundo', 'terceiro'];
myArray.each( myIterator );
   

Mas depois nós não estaríamos fazendo como os "cool kids" da escola, certo? Mais seriamente, entretanto, este último formato é mais simples para entender mas nos leva a ir direto ao código da função myIterator. É legal nós podermos ter a lógica da função iterator no mesmo lugar onde ela é chamada. Além disso, nesse caso, nós não iremos necessitar da função iterator em nenhum outro lugar em nosso código, então nós podemos transformá-lo em uma função anônima sem nenhuma penalidade. 

Vamos olhar o exemplo original novamente com algum destaque para de alguma deixar as coisas mais claras.

    
var myArray = ['primeiro', 'segundo', 'terceiro'];
myArray.each( function(item, index){
 alert('O item na posição #' + index + ' é:' + item);
} );
   

This é isto mas às vezes isto é também aquilo

Um dos problemas mais comuns que nós temos com JS quando nós começamos a escrever nosso código é o uso da palavra chave this. Isso pode ser realmente um detonador.

Como nós mencionamos anteriormente, uma função também é um objeto em JS, e algumas vezes nós não percebemos que estamos passando uma função por aí.     

Pegue esse trecho de código como um exemplo.


function buttonClicked(){
 alert('botão ' + this.id + ' foi clicado');
}

var myButton = document.getElementById('someButtonID');
var myButton2 = document.getElementById('someOtherButtonID');
myButton.onclick = buttonClicked;
myButton2.onclick = buttonClicked;
   

Pelo fato da função buttonClicked ser definida fora de qualquer objeto nós tendemos a acreditar que a palavra chave this irá conter uma referencia ao objeto window ou document ( assumindo que esse código está no meio de uma página HTML visualizada em um browser. )

Mas quando nós executamos esse código nós vemos que ele funciona como planejado e exibe o id do botão que foi clicado. O que acontece aqui é que nós fizemos com que o método onclick de cada botão contenha uma referencia ao objeto buttonClicked, substituindo qualquer coisa que estava lá antes. Agora quando o botão é clicado, o browser irá executar algo similar a seguinte linha.

  myButton.onclick();
 

Isso não é tão confuso após tudo isso, não é mesmo ? Mas veja o que aconteça quando você começa a ter outros objetos para lidar e quando você deseja agir sobre esse objeto com eventos como o clique nos botões. 


var myHelper = {
 
 formFields: [ ],
 
 emptyAllFields: function(){
  for(i=0; i<this.formFields.length; i++){
   var elementID = this.formFields[i];
   var field = document.getElementById(elementID);
   field.value = '';
  }
 }
};

// mostre com quais campos do form nós queremos trabalhar
myHelper.formFields.push('txtName');
myHelper.formFields.push('txtEmail');
myHelper.formFields.push('txtAddress');

// limpando os text boxes:
myHelper.emptyAllFields();

var clearButton = document.getElementById('btnClear');
clearButton.onclick = myHelper.emptyAllFields;
   

Então você pensa, legal, agora eu posso clicar no botão Clear em minha página e aqueles três textboxes serão limpos. Então você tenta clicar no botão e obtêm um erro em tempo de execução (runtime). O erro será relacionado a (adivinha?) palavra chave this. O problema é que o this.formFields não está definido se o this contém uma referência ao botão, o que é exatamente o que está acontecendo. Uma solução rápida seria reescrever nossa última linha de código.   


clearButton.onclick = function(){ myHelper.emptyAllFields(); };
   

Dessa forma nós criamos uma nova função que chama nosso método auxiliar com o contexto do objeto auxiliar.


Esse artigo ainda não foi finalizado. Se você encontrou exemplos quebrados, informações incorretas, links quebrados, etc., por favor e eu irei tentar corrigí-lo o mais rápido possível.