sexta-feira, 17 de agosto de 2007

O atributo length de objetos e arrays em javascript

Eu não resisti e usei Javascript essa ultima semana inteira - É a minha confissão de hoje na reunião de javascriptólatras anônimos - Passei um perrengue danado por causa de suas peculiaridades. Tudo causado por falta de atenção a uma das pequenas regrinhas que essa linguagem viciante tem. Para evitar que outros passem pelo mesmo problema, compartilho aqui minha dor.

Em primeiro lugar é preciso verificar que somente arrays têm o atributo length. Em segundo que arrays podem se comportar como objetos, e objetos podem se comportar como arrays, e que arrays são objetos. Entendeu? Bom, vamos aos exemplos pra ficar mais claro.
 var eu = { nome:"Tiago",idade:25 }; //Criando um objeto dinâmico com {}
alert( eu["nome"] ) //Acessando como se fosse um mapa
alert( eu.idade ) //Acessando como um atributo
eu.sexo = "Sempre"; //Criando um novo atributo
alert( eu["sexo"] );
eu["altura"] = 1.75; //Criando um novo atributo como mapa
alert( eu.altura );
No exemplo acima eu crio um objeto dinamicamente utilizando chaves além de acessar e criar novos atributos, de duas formas possíveis: Como se fosse um mapa, utilizando o operado colchetes, e da maneira mais comum, que é utilizando o operador ponto. Vamos seguir então para o próximo exemplo:
 var eu = []; //Criando um array com []
eu.nome = "Tiago"; //Setando atributo em um array? Que coisa estranha
eu["idade"] = 25 //Não era um array, por que está aceitando uma chave como se fosse um mapa?
alert( eu["nome"] ) //Acessando como se fosse um mapa
alert( eu.idade ) //Acessando como um atributo
eu.sexo = "Sempre"; //Criando um novo atributo
alert( eu["sexo"] );
eu["altura"] = 1.75; //Criando um novo atributo como mapa
alert( eu.altura );
Neste exemplo criamos um array ao invés de um objeto, e qual não é nossa surpresa ao perceber que, exceto pela incialização, o comportamento dele fica exatamente igual ao de um objeto. Isso ocorre porque um array é um objeto. Até aí tudo parece maravilhoso. A coisa desanta quando você resolve usar o comportamento de array, num array tratado como objeto. Vejamos:
 var eu = []; 
alert(eu.length); //Tamanho 0 oras, não coloquei nada aqui ainda
eu["nome"] = "Tiago"; //Inserindo um atributo
alert(eu.length); //Tamanho 0?? E o atributo que botei?
eu.idade = 25; //Inserindo outro atributo
alert(eu.length); //Tamanho 0 de novo????
eu[0] = "primeiro indice"; //Adicionando elementos ao array
eu[1] = "segundo indice";
alert(eu.length); //Ahhh agora sim
eu.push("terceiro indice");
eu.push("quarto indice");
alert(eu.length); //Ahhh agora sim de novo
No exemplo acima podemos perceber o quão confuso começa a ficar. Basicamente o atributo length passa a só funcionar quando inserimos elementos no array. Ou seja, os atributos inseridos no array ficam fora do array. Isso fica mais evidente fazendo as iterações como as do exemplo abaixo:
 //agora vamos iterar só pelos elementos do array
for(i=0; i {
alert("elemento do array: " + eu[i]);
}
//agora vamos iterar sobre tudo que tiver aí
for(i in eu)
{
alert("atributo do objeto: " + eu[i]);
}
//De novo somente elementos do array
while(eu.length>0)
{
alert("Remove elemento: " + eu.pop());
}
Na primeira eu itero sobre números, logo somente sobre os elementos do array. No segundo eu itero sobre as chaves, sejam elas numéricas, sejam de texto. E finalmente no terceiro eu itero removendo os elementos do array. Mas ainda sim podem ocorrer algumas curiosidades que podem nos confundir, veja abaixo:
 var eu = []; 
eu[0] = "primeiro indice";
eu[1] = "segundo indice";
eu["1"] = "substitui segundo indice"; //Isso aqui é o mesmo que eu[1]
alert(eu[1]); //Vai alertar a mesma coisa que o alerta de baixo
alert(eu["1"]);
eu["length"] = 0; //Vou sacanear o array agora
alert(eu.length); //Zerei o tamanho
alert(eu[4]); //Ih!!! Apagou tudo
Perceba que adicionar um elemento ao indice 1 como número é o mesmo que adicionar ao 1 como string. Além disso, veja que podemos alterar o valor do tamanho, atributo length, do array somente atribuindo um valor a ele. Isso pode causar grandes problemas, ainda mais se você estiver guardando dados digitados pelo usuário como se o array fosse um mapa.

Um exemplo simples é um usuário preenchendo um formulário com campo de nome e valor, ao clicar em um botão esses dados são inseridos no array como se fosse um mapa. Digamos que ele preencha da seguinte maneira:
Nome     | Valor: 
---------------------
"país" | "brasil"
10 | "isso"
7 | "hoje"
"olha" | "caso"
"length" | 0
Neste caso quando ele digitar o nome 10, o array passará a ter 10 posições, mas no momento que ele digitar "length" com o valor 0, os dados que ele inseriu com nome 10 e 7 desaparecerão. Para debugar isso você terá que arrancar vários tufos de seu cabelo.

E é aí finalmente que chegamos ao ponto crucial. Para evitar esse tipo de problema o ideal é deixar bem separado o que é array, inicializando com [] e o que é objeto inicializando com {}. Isso porque para objetos, um atributo com nome length não tem poder algum, exceto se você implementá-lo para que tenha.

4 comentários:

  1. Maneiro esse post, não sabia que dava pra fazer isso tudo com javascript, eu consigo mesmo alterar a propriedade do tamanho do array como o código eu["length"] = 0; ?
    Sinistro.

    Abraços.

    ResponderExcluir
  2. Muito bom o post... pena que não consegui resolver meu problema que é contar os itens de um objeto

    ResponderExcluir
  3. Desenterrando o post..rs
    Pra validar a quantidade de atributos num objeto Javascript, use: Object.keys(nome_do_objeto)

    ResponderExcluir