Front End

28 jun, 2011

Entendendo Arrays no JavaScript

Publicidade

Neste artigo, vamos abordar de forma mais detalhada o que são Arrays no JavaScript.

O que é um Array no JavaScript?

Um mapa numericamente
indexado de valores.

Tradicionalmente, um Array
reserva uma alocação contínua de memória de tamanho predefinido. No JavaScript, esse não é o caso. Um Array no JavaScript é simplesmente um objeto
glorificado com um construtor único, com uma sintaxe literal e com um conjunto adicional de
propriedades e de métodos herdados de um protótipo de Array. Isso significa fazer
um pequeno sacrifício de performance, mais do que compensado pela facilidade de
uso e pelo poder de seus utilitários. Diferentemente de suas contrapartes em
certas outras linguagens, os Arrays no JavaScript são ótimos de usar – e isso é
algo verdadeiro.

Como crio um Array no JavaScript?

Vamos começar da melhor
maneira. No JavaScript, sempre que há uma sintaxe literal para criação de
objetos, geralmente faz sentido usá-la.

01	//create a new, empty array
02 var a = [];
03 //add members to an existing array
04 a[0] = "Bob";
05 a[1] = "Mary";
06 a[2] = "Joe";
07 //or simply use push
08 a.push("Jane");
09 a.push("Carlos");
10 //create a new array and predefine some members
11 var b = ["Bob", "Mary", "Joe", "Jane", "Carlos"];

Alternativamente, podemos usar
a sintaxe new Constructor. Fora a óbvia desvantagem de 5-9 toques
adicionais, há um aspecto mais sério no que diz respeito à ambiguidade: 

1	//create a new array with 8 undefined members
2 var a = new Array(8);
3 //create a new array containing two predefined elements
4 var b = new Array(8,9);
5 a.length; //8
6 b.length; //2
7 a[0]; //undefined
8 b[0]; //8

Essas duas formas parecem
notavelmente similares, mas produzem resultados inteiramente diferentes. Além
disso, digamos que alguém edite o segundo comando porque deseja somente predefinir
um elemento, o número 8, no Array b. É muito parecido com o que faria se
desejasse modificá-lo como segue (e quem poderia culpá-lo?):

1	//create a new array containing one predefined element
2 var b = new Array(8); //Wrong!

Naturalmente,
isso não faz o que queremos. A única forma de predefinir um Array com um
número primitivo é usar a sintaxe literal (Obrigado
Peter e Dimitry pelo
esclarecimento).

Há alguma vantagem em usar a sintaxe new Array?

Bem, isso significa que você
pode definir o comprimento do seu Array no momento de sua criação. Mas, como o
Array no JavaScript não requer uma alocação de memória prévia, e eles podem ser
estendidos a qualquer momento, esse é um requisito questionável. (Diversas
pessoas indicaram que o webkit entre outros construiram uma otimização quando o
comprimento do Array é predefinido – embora não haja nada no spec que sugira
isso).

Que tipos de dados um Array contém?

Um Array pode conter qualquer
objeto do tipo primitivo. Dados de tipos diferentes podem coexistir no mesmo
Array. 

Como acesso um elemento Array?

Elementos Array são simplesmente propriedades de
objetos e são acessados da mesma forma que outras propriedades de objetos. Uma vez que identificadores de propriedade
são sempre strings, o índice Array também é uma string, e não um número.
Contudo, quando você usa uma notação de subscript (colchetes) para acessar a
propriedade, um número literal também pode ser usado, desde que seja limitado
por uma string pelo interpretador. Navegar por rotação de ponto não funcionará
para membros de acesso do Array, porque identificadores de propriedades literais não
podem iniciar com um número (novamente, todos esses comportamentos derivam das
regras de propriedade genérica dos objetos, elas não são específicas para o
Array).

1	var a = ["banana", Math.min, 4, "apple"];
2 a['1']; //min()
3 a[2]; //4

Como devo iterar nos elementos de um Array?

Tipicamente faz sentido usar o loop for padrão:

1	var a = ["banana", Math.min, 4, "apple"];
2 for (var i=0; i < a.length; i++) {
3 console.log(a[i]);
4 }

Se o seu Array é longo, você
pode ficar aborrecido com o trabalho adicional de solicitar o Array.lenght em
toda iteração. Para contornar isso, você pode definir inicialmente o
comprimento do Array.

1	var a = makeBigArray();
2 var aLength = a.length;
3 for (var i=0; i < aLength; i++) {
4 console.log(a[i]);
5 }

Usar um comando for…in para iteração de
Arrays não é aconselhável porque você pode também pegar propriedades enumeráveis do prototype (veja
abaixo).

Quais propriedades são únicas para as Arrays?

A propriedade mais importante
da Array é o comprimento (strings e funções também tem comprimento, mas a
definição de comprimento da Array é única)

O ECMA specifica:

A  length property deste objeto Array é semprenumericamente maior que o nome de toda propriedade cujo nome é um índice Array.

Em outras palavras, é (o
valor numérico do último índice)
+ 1

Arrays não têm limitação
superior. Você pode adicionar um elemento em um índice maior que  (comprimento -1) e o lenght property será modificado baseado na definição acima. Os Arrays têm um
comprimento máximo, mas ele é grande demais para que você se preocupe.

1	var a = [3,4,1];
2 a.length; //3
3 a[20] = 2;
4 a.length; //21
5 //element indexes 3-19 automatically created with value initialized to undefined
6 a[18]; //undefined

Arrays são limitadas a
zero. Se você tentar adicionar um valor
a um índice negativo, você somente estará escrevendo a propriedade de um objeto
comum. (veja também “Arrays associativos” abaixo).

1	var a = [];
2 a[-1] = "giraffe";
3 a[-1]; //"giraffe"; //because still using a regular object property accessor
4 a.length; //0
5 a.toString(); //""

Você pode manipular o conteúdo de um Array existente
atualizando o valor de seu comprimento.
Se você reduzir o lenght property de
um Array existente, partes com índices
maiores ou iguais ao novo comprimento são descartadas (essa é a única forma de
remover índices de um  Array – você pode
apagar um elemento, mas isso somente
apagará o valor e deixará o índice em
seu lugar – ou seja, seu Array fica “espaçado” = fica com buracos.

1	var a = [0,1,2,3,4,5,6];
2 a.length; //7
3 a.length = 5;
4 a.toString(); //"0,1,2,3,4"
5 a[6]; //undefined

De modo contrário, se você aumentar em n o comprimento de um
Array existente, seu Array parece
obter n novos membros, cada um com
seu valor inicializado como
indefinido – contudo, como Dmitry Soshnikov ressalta, essa é a resposta padrão
para acessar uma propriedade não existente. Na realidade, nada mudou, com
exceção do comprimento do Array.

1	var a = [0,1,2,3,4,5,6];
2 a.length; //7
3 a[9]; //undefined
4 a[59]; //undefined
5 a.length = 10;
6 a.toString(); //"0,1,2,3,4,5,6,,,"
7 a[9]; //undefined
8 a[59]; //undefined

Há duas pseudo-propriedades
adicionais dos Arrays: index e input. Essas propriedades estão presentes apenas em Arrays criadas
por expressões regulares.

Quais métodos são herdados do prototype Array?

O Array proporciona muitas utilidades bastante
úteis. São tantas que é difícil para detalharmos, e você está provavelmente
familiarizado com a maioria das funções pré-ECMA 5. Os métodos Array seguintes
estão disponíveis na maioria das últimas versões dos principais browsers.

concat faz cópia simples do Array e adiciona os argumentos
join cria um string do Array. Adiciona o argumento como cola entre cada membro do Array.
shift remove e retorna o primeiro elemento
pop remove e retorna o ultimo elemento
unshift anexa os argumentos na frente do Array 
push anexa os argumentos no final do Array 
reverse inverte o Array sem copiá-lo
slice faz cópia simples da porção do Array delimitada por argumentos do índice
splice remove elementos especificados do Array, e os substitui com argumentos adicionais opcionais
sort classifica o Array sem copiá-lo, opcionalmente usando um argumento comparador
toString chama join sem passar um argumento

O ECMA 5 especifica um conjunto adicional de funções
de ordem superior, todas já tendo sido implementadas por todos os principais
browsers exceto  IE<=8 (mas a preview
do IE9 os implementa). Muitos desses novos métodos já serão familiares para
aqueles que fizerem uso dos principais frameworks do JavaScript:

indexOf retorna o primeiro elemento igual ao valor
especificado, ou -1 se nenhum for encontrado
lastIndexOf
retorna o último elemento igual ao valor especificado, ou -1 se nenhum for
encontrado
every retorna
verdadeiro se a função fornecida retorna verdadeiro quando aplicada a todos
elementos
some retorna verdadeiro se a função fornecida retorna verdadeiro quando aplicada
a pelo menos um elemento 
forEach aplica a função fornecida para todos elementos do Array
map cria um novo Array contendo os resultados da aplicação da função a todos
elementos do Array
filter cria um novo Array contendo todos os elementos para os quais a função
fornecida retorna verdadeiro
reduce aplica uma função simultaneamente para dois valores do Array (da
esquerda para a direita) de forma a reduzi-los a um valor único (nota: reduce
tinha um significado diferente em versões mais antigas do Prototype.js)
reduceRight aplica uma função simultaneamente para dois valores do Array (da
direita para a esquerda) de forma a reduzi-los a um valor único.

Como sei se meu objeto é do tipo Array?

A eterna questão. O problema é que quando você usa typeof no JavaScript para um Array,
ele retorna “object”. Eu quase escrevi quase um post inteiro em um blog sobre este
tópico. Felizmente o
kangax já fez isso (inglês). O resultado é a
versão no estado da arte de isArray, que é mais simples e robusta que qualquer um
da enorme quantidade de seus predecessores. Também é a implementação
correntemente usada tanto em jQuery quanto em  Prototype.js.

1	function isArray(o) {
2 return Object.prototype.toString.call(o) === "[object Array]";
3 }

E sobre “Associative Arrays”?

O JavaScript não suporta Associative Arrays.  Esse é um erro comum devido ao fato de que o que se
segue parece funcionar como um Array não indexado numericamente.

1	var a = new Array();
2 a['cat'] = 4;
3 a['spider'] = 8;
4 a['centipede'] = 100;
5 a['spider']; //8

Na verdade, esse código, ainda
que não incorreto, é um uso impróprio do objeto Array.

Nenhum membro Array é
adicionado (Array.length é 0). Tudo que fizemos foi definir propriedades em um
objeto comum – essencialmente criamos uma tabela hash.

Para ilustrar esse
ponto, podemos substituir Array por qualquer outro objeto e obter o mesmo
resultado.

1	var a = Boolean;
2 a['cat'] = 4;
3 a['spider'] = 8;
4 a['centipede'] = 100;
5 a['spider']; //8

Além do mais, construir um
hash sobre um objeto Array é potencialmente perigoso. Se alguém estender o Array.prototype
com propriedades enumeráveis (como a biblioteca Prototype.js faz, por exemplo), estas serão lidas durante
iterações for…in, devastando sua
lógica (ou pelo menos fazendo com que você tenha que usar o método hasOwnProperty)

Construa hashes sobre
Objeto e nada mais, já que por convenção Object.prototype não é aumentado.

Como o comportamento Array é descrito no padrão ECMA?

Primeiramente, o ECMA especifica um Array.Prototype que permeia os
Arrays com suas propriedades únicas. Em segundo lugar, o ECMA define regras
especializadas para a definição de property quando aplicadas nos Arrays.

Um entendimento dos métodos
internos [[Get]] e [[Put]] será útil nesse ponto. Por especificação, todo
objeto JavaScript tem esses métodos – eles são essencialmente as funções de
baixo nível pelas quais o JavaScriptEngine recupera e atualiza as propriedades
do objeto. O método [[Get]] de um  Array
não é diferente do método [[Get]]  do
Objeto – daí você é capaz de acessar partes do Array da mesma forma que você acessaria a property de qualquer
objeto. Contudo, o método [[Put]] de um Array é especializado, e isso é o que
faz um Array único.

Richard Cornford explica isso bem: ”O método
[[Put]] deve ter interesse nas
atribuições na propriedade length de
um Array, porque se for menor que o valor corrente, pode ser necessário apagar
propriedades do Array. De outra forma, o
argumento do nome da propriedade é convertido em um número usando a função
interna ToUnit32 e, se esse número não for menor que o valor corrente da
propriedade length, então a
propriedade length do Array é redefinido para o valor desse número mais um.

Sempre que o nome da
propriedade fornecida para o método [[Put]] da Array não for uma representação string de um número inteiro (não
assinado 32 bit) de condição 8 no algoritmo para o método [[Put]], evita a
necessidade de considerar a iteração com a propriedade length do Array”.

Leitura complementar:

David Flanagan: JavaScript, The Definitive Guide
(O’Reilly Press)

Patrick Hunlock: Mastering JavaScript Arrays
Andrew Dupont: JavaScript “Associative Arrays”
Considered Harmful

Juriy Zaytsev (“kangax”): `instanceof` considered harmful (or how
to write a robust `isArray`)

Juriy Zaytsev (“kangax”): How ECMAScript 5 still does not allow to
subclass an Array

Richard Cornford: (guest comment)
ECMA-262 5th Edition
section 15.4
Mozilla Core JavaScript Reference: Array

 ?

Texto original disponível em http://javascriptweblog.wordpress.com/2010/07/12/understanding-javascript-Arrays/