Skip to content
This page has been auto-translated and may contain errors.View in English

Arrays

docs.scrimba.com

Você tem um carrinho de compras, uma lista de pontuações, os nomes de todos em um grupo de chat. Uma variável segura um valor, então armazenar uma lista dessa forma significa pilhas de nomes separados: price1, price2, price3, e nenhuma maneira limpa de fazer loop sobre eles ou perguntar quantos há. Um array é a solução. Ele segura uma lista ordenada de valores sob um único nome, e conhece seu próprio comprimento, então você pode adicionar a ele, ler qualquer item e percorrer o tudo em poucas linhas.

Fazendo e lendo um array

Um array é uma lista ordenada de valores. Você escreve um com colchetes, com os valores separados por vírgulas:

js
const prices = [4, 9, 2, 7];

Essa é uma variável, prices, segurando quatro números. Para ler um único valor você usa sua posição, chamada de índice, entre colchetes. O detalhe que confunde quase everyone no começo: a contagem começa em zero, não um.

js
const names = ["Marina", "João", "Yuki"];
console.log(names[0]); // Marina
console.log(names[1]); // João

Para perguntar quantos items um array tem, use .length:

js
console.log(names.length); // 3

Como a contagem começa em zero, o último item fica em length - 1, não em length. Então o último nome aqui é names[2], e names[3] ficaria para além do final.

Um array é uma coleção ordenada de valores sob um único nome. Você cria um com um literal de colchetes, e lê qualquer item pelo seu índice, sua posição contada a partir de zero:

js
const prices = [4, 9, 2, 7];
console.log(prices[0]); // 4
console.log(prices[2]); // 2

A indexação baseada em zero é a fonte da maioria dos bugs iniciais de off-by-one, então vale a pena corrigir isso em sua mente agora: o primeiro item está no índice 0, e o último está em length - 1.

js
console.log(prices.length); // 4
console.log(prices[prices.length - 1]); // 7, o último item

Ler um índice que não existe não lança um erro. prices[10] lhe dá undefined, que é a forma do JavaScript de dizer "não há nada aqui". Esse resultado silencioso é conveniente e ocasionalmente uma armadilha, porque um erro de digitação em um índice lhe dá undefined em vez de uma falha ruidosa.

Um array é uma coleção ordenada e indexada criada com um literal de colchetes. Os itens são lidos por índice contado a partir de zero, e .length informa a contagem:

js
const prices = [4, 9, 2, 7];
console.log(prices[0]); // 4
console.log(prices.length); // 4
console.log(prices[prices.length - 1]); // 7, o último item

Duas propriedades valem a pena ser nomeadas com precisão. Primeiro, .length não é um tamanho fixo que você declarou antecipadamente; ele rastreia o índice mais alto em uso e é atualizado conforme você adiciona e remove itens. Segundo, uma leitura fora do intervalo retorna undefined em vez de lançar, então um erro de off-by-one aparece mais tarde como undefined fluindo através de sua lógica, não como uma exceção no ponto do erro. Quando você quer que a falha seja ruidosa, proteja o índice contra length você mesmo.

Arrays podem conter tipos de valores mistos ([1, "dois", true]), mas uma base de código funcionando quase sempre mantém um array para um tipo de coisa, porque cada pedaço de código que lê o array então sabe o que está recebendo. A consistência aqui é uma decisão de legibilidade, não uma regra de linguagem.

JunoFazendo e lendo um array Um array é uma lista de valores entre colchetes sob um único nome. Leia um item por sua posição com names[0], e lembre-se que a contagem começa em zero, então o primeiro item é [0], não [1]. Esse zero me confundiu por uma semana, então verifique sempre que sua lista ficar deslocada por um. Use .length para contar os itens, e o último vive em length - 1.
JunoFazendo e lendo um array Um array segura uma lista ordenada sob um único nome, e você lê itens por índice contado a partir de zero. O último item está em length - 1, que é de onde vêm a maioria dos bugs iniciais de off-by-one. Ler além do final retorna undefined em vez de errar, então um índice digitado errado fica silencioso em vez de ruidoso. Tenha isso em mente quando um valor aparecer como undefined sem motivo claro.
JunoFazendo e lendo um array Literal de colchetes, índice baseado em zero, e .length que rastreia o índice mais alto em uso em vez de um tamanho que você declarou. Uma leitura fora do intervalo devolve undefined em vez de lançar, então um erro de off-by-one viaja para baixo antes de morder. Proteja o índice contra length você mesmo quando quer que essa falha seja ruidosa. E mantenha um array para um tipo de coisa, não porque a linguagem força, mas porque cada leitor do array então sabe o que ele segura.

Alterando um array

Arrays são feitos para mudar. Os dois que você mais acessa adicionam ou removem no final: push adiciona um item, pop remove o último.

js
const cart = ["maçãs", "pão"];
cart.push("leite");
console.log(cart); // ["maçãs", "pão", "leite"]
cart.pop();
console.log(cart); // ["maçãs", "pão"]

O par correspondente funciona no início: unshift adiciona ao início, shift remove o primeiro item.

js
cart.unshift("ovos");
console.log(cart); // ["ovos", "maçãs", "pão"]

Você também pode substituir um item diretamente atribuindo a seu índice:

js
cart[0] = "bananas";
console.log(cart); // ["bananas", "maçãs", "pão"]

Para encontrar algo, includes lhe diz sim ou não, e indexOf lhe diz onde está (ou -1 se não estiver lá):

js
console.log(cart.includes("maçãs")); // true
console.log(cart.indexOf("pão")); // 2

Uma coisa que vale a pena saber cedo: um array const ainda pode ser alterado. const o impede de reatribuir o nome cart para um novo array inteiro, mas a lista para a qual aponta ainda está aberta para push, pop e atribuição de índice.

Arrays são feitos para serem modificados no lugar. Dois pares de métodos fazem a maioria do trabalho, um em cada final:

js
const cart = ["maçãs", "pão"];
cart.push("leite"); // adicionar ao final
cart.pop(); // remover do final
cart.unshift("ovos"); // adicionar ao início
cart.shift(); // remover do início

push e pop são o par barato e comum; você os alcança constantemente. shift e unshift tocam o início, o que significa que cada outro item tem que se mover para cima ou para baixo um slot, então em um array grande eles fazem mais trabalho. Prefira o final quando a ordem é sua para escolher.

Você pode sobrescrever qualquer posição atribuindo a seu índice, e você pode encontrar itens de duas maneiras:

js
const names = ["Marina", "João", "Yuki"];
names[1] = "Priya";
console.log(names.indexOf("Yuki")); // 2, ou -1 se ausente
console.log(names.includes("Marina")); // true

Alcance includes quando você só se importa se algo está presente, e indexOf quando você também precisa da posição. Ambos comparam com ===, então indexOf("2") não encontrará o número 2.

Um array const ainda é totalmente mutável: const trava o nome, não o conteúdo. Você não pode apontar cart para um novo array, mas você pode push para o que ele segura o dia todo.

Arrays são mutáveis, e os métodos centrais alteram o array no lugar em vez de retornar um novo. push/pop operem no final, shift/unshift no início:

js
const cart = ["maçãs", "pão"];
cart.push("leite"); // retorna o novo comprimento, muta cart
const removed = cart.pop(); // retorna o item removido, muta cart

O final e o início não são iguais em custo. push e pop tocam um único slot. shift e unshift reindexam cada elemento restante, que é trabalho linear no comprimento do array, então um loop quente que repete shift em um array grande está discretamente fazendo muito mais do que parece. Quando a ordem está sob seu controle, mantenha o movimento no final.

Atribuir a um índice escreve no lugar, e a busca se divide pela necessidade: includes por presença, indexOf pela posição (com -1 para ausente). Ambas usam igualdade estrita (===), então o tipo importa:

js
const ids = [1, 2, 3];
console.log(ids.indexOf("2")); // -1, string não corresponde ao número
console.log(ids.includes(2)); // true

O ponto const vale a pena ser declarado exatamente, porque é o primeiro lugar onde o modelo de referência aparece: const vincula o nome a um array e proíbe revinculá-lo, mas não diz nada sobre o conteúdo do array. cart.push(...) muta o objeto para o qual o nome aponta, que é uma operação diferente de cart = [...]. Essa distinção é o assunto inteiro da próxima seção.

JunoAlterando um arraypush e pop adicionam e removem no final; unshift e shift fazem o mesmo no início. Atribua a um índice como cart[0] = "bananas" para substituir um item, e use includes ou indexOf para encontrar um. A parte que me surpreendeu: um array const ainda pode mudar, porque const só o impede de trocar o array inteiro por um novo.
JunoAlterando um array Dois pares cobrem a maioria das edições: push/pop no final, shift/unshift no início. O par frontal move cada outro item para cima, então favoreça o final quando a ordem é sua. includes verifica presença, indexOf dá a posição, e ambos combinam com ===, então "2" não encontrará o número 2. E const trava o nome, nunca o conteúdo, então você pode fazer push para um array const o dia todo.
JunoAlterando um array Esses métodos mutam no lugar: push/pop tocam um slot, shift/unshift reindexam o array inteiro, então um loop que continua mudando um array grande está fazendo trabalho linear cada vez. includes e indexOf ambas usam ===, então um índice de string não corresponderá a um número. E const apenas proíbe revinculação do nome; cart.push() mutando o array é algo diferente de cart = []. Essa diferença é exatamente a história de referência chegando depois.

Fazendo loops sobre um array

Uma vez que você tem uma lista, você geralmente quer fazer algo com cada item. A forma mais limpa de ler um array é for...of, que lhe dá um item de cada vez:

js
const prices = [4, 9, 2, 7];
for (const price of prices) {
  console.log(price);
}
// 4
// 9
// 2
// 7

Você não gerencia um índice ou um contador. for...of lhe dá cada valor em ordem, e você o nomeia (price) como quiser. Se você já conheceu loops, esse é o mais amigável para arrays.

Há também forEach, que executa uma pequena função uma vez para cada item:

js
const names = ["Marina", "João", "Yuki"];
names.forEach(function (name) {
  console.log("Olá " + name);
});

Ambas percorrem o array inteiro. Alcance for...of quando você quer ler de forma limpa, e você conhecerá forEach e seus primos novamente quando aprender a transformar arrays.

O padrão moderno para ler um array é for...of. Ele lhe dá cada valor diretamente, sem índice para gerenciar e nenhuma chance de um deslize de off-by-one:

js
const prices = [4, 9, 2, 7];
let total = 0;
for (const price of prices) {
  total = total + price;
}
console.log(total); // 22

Use for...of quando você quer os valores em ordem e código limpo. Se você também precisa do índice (digamos, para numerar uma lista), o loop for de contagem clássico ou o segundo argumento de forEach lhe dá.

forEach executa um callback, uma função que você lhe entrega, uma vez por item:

js
const names = ["Marina", "João", "Yuki"];
names.forEach((name) => {
  console.log("Olá " + name);
});

forEach lê bem para um efeito colateral por item, como logging. O que ele não pode fazer é break para sair cedo, então quando você precisa parar no meio, use for...of. Construir um novo array a partir de um antigo é um trabalho diferente com suas próprias ferramentas, coberto em array methods.

Para uma leitura simples, for...of é a escolha idiomática: ela itera valores diretamente, contorna aritmética de índice e suporta break e continue:

js
const prices = [4, 9, 2, 7];
let total = 0;
for (const price of prices) {
  if (price > 8) continue;
  total = total + price;
}
console.log(total); // 13, pulou o 9

forEach leva um callback por elemento e lê de forma limpa para efeitos colaterais, mas vem com duas limitações que vale a pena conhecer antes de padronizá-lo. Ele não pode break, então sempre executa até o final, e ignora um valor retornado, então é a ferramenta errada quando você está tentando produzir um resultado. Para isso, os métodos transformadores (map, filter, reduce) existem, e array methods é onde eles pertencem.

js
const names = ["Marina", "João", "Yuki"];
names.forEach((name, index) => {
  console.log(index + ": " + name);
});

O segundo argumento do callback é o índice, que é como você recupera a posição sem um contador manual. A regra de ouro: for...of quando você precisa de fluxo de controle ou está acumulando em uma variável externa, forEach quando um efeito colateral por item lê mais claramente, e um método transformador quando você quer um novo array devolvido em vez de uma mutação ou um log.

JunoFazendo loops sobre um arrayfor...of é a forma limpa de percorrer um array: ele lhe entrega cada item em ordem e você nunca toca um índice. forEach executa uma pequena função uma vez por item, o que é útil também. Ambas percorrem a lista inteira, então escolha for...of quando você quer ler diretamente por ela. Você verá mais ferramentas de array uma vez que começar a transformá-los.
JunoFazendo loops sobre um arrayfor...of é o padrão para ler um array: valores em ordem, sem índice para gerenciar, e break funciona quando você precisa parar cedo. forEach executa um callback por item e lê bem para efeitos colaterais como logging, mas não pode break, então sempre executa até o final. Construir um novo array a partir de um antigo é um trabalho separado com seus próprios métodos. Em caso de dúvida, for...of.
JunoFazendo loops sobre um arrayfor...of itera valores diretamente e suporta break e continue, então ganha sempre que você precisa de fluxo de controle ou está acumulando em uma variável externa. forEach lê de forma limpa para um efeito colateral por item mas não pode fazer break e ignora valores retornados, o que o torna a ferramenta errada para produzir um resultado. Quando você quer um novo array devolvido, alcance os métodos transformadores em vez de um loop. Seu segundo argumento de callback lhe dá o índice se você precisar da posição.

Arrays são mantidos por referência

Aqui está um comportamento que surpreende as pessoas, então vale a pena ver cedo. Quando você copia uma variável de array para outro nome, você não obtém um segundo array. Ambos os nomes apontam para o mesmo.

js
const original = [1, 2, 3];
const copy = original;
copy.push(4);
console.log(original); // [1, 2, 3, 4]

Fazer push para copy alterou original, porque nunca houve dois arrays. O = entregou ao segundo nome a mesma lista, não uma cópia nova dela. Isso é o que as pessoas querem dizer quando dizem que arrays são mantidos por referência: a variável segura um ponteiro para a lista, não a lista em si.

Para obter uma cópia real e separada, espalhe o array para um novo com ...:

js
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3], intocado

Um número ou uma string é mantido por valor: copiar a variável copia o valor. Um array é diferente. É mantido por referência, então copiar a variável copia o ponteiro, e ambos os nomes acabam apontados para o mesmo array.

js
const original = [1, 2, 3];
const shared = original;
shared.push(4);
console.log(original); // [1, 2, 3, 4], o mesmo array

Este é o mesmo fato por trás da nota const anterior: um array é um objeto, e uma variável segura uma referência a esse objeto, não o objeto em si. Mutar através de qualquer nome muta o array único que eles compartilham.

Quando você quer uma cópia independente, espalhe-a para um novo array. O ... copia cada item:

js
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3], separado

Esta é uma cópia superficial: o novo array é sua própria lista, mas se os itens fossem eles próprios arrays, ambas as cópias ainda compartilhariam esses arrays internos. Com números e strings simples, superficial é tudo o que você precisa.

Primitivos (números, strings, booleanos) são mantidos por valor; arrays são objetos e mantidos por referência. Uma variável que "contém" um array na verdade segura uma referência a ele, então a atribuição copia a referência, e cada nome apontando para esse array vê cada mutação:

js
const original = [1, 2, 3];
const alias = original;
alias.push(4);
console.log(original); // [1, 2, 3, 4]
console.log(original === alias); // true, mesmo array

Isto é por que const e mutabilidade são preocupações separadas: const fixa a referência, mutação muda o que a referência aponta. É também o argumento por preferir imutabilidade, produzindo um novo array em vez de alterar um no lugar. Estado mutável compartilhado significa que uma função que muta um array que recebeu discretamente a altera para cada outro detentor dessa referência, que é uma classe de bug que é difícil de rastrear. Retornar um novo array mantém os dados de cada chamador como seus próprios.

O operador spread faz uma cópia superficial: um novo array externo com os mesmos itens copiados para dentro.

js
const original = [1, 2, 3];
const copy = [...original];
console.log(copy === original); // false, arrays diferentes

Superficial é a palavra que importa. O novo array é independente, mas arrays aninhados dentro dele ainda são compartilhados por referência, então uma estrutura profunda precisa de mais do que um spread. Com arrays de primitivos, que é tudo o que você tem até agora, um spread é uma cópia completa.

Quanto a escolher a estrutura em tudo: um array é a ferramenta certa quando você tem uma lista ordenada de coisas semelhantes e a posição ou contagem importa, uma fila de preços, uma sequência de nomes. Quando você ao invés disso quer procurar valores por uma chave significativa em vez de uma posição, é para o que um object serve, e uma function é onde muito desse trabalho de array acaba vivendo.

JunoArrays são mantidos por referência Copiar uma variável de array com = não cria um segundo array; ambos os nomes apontam para o mesmo, então uma mudança através de qualquer um aparece em ambos. Esse me pegou mais de uma vez. Quando você quer uma cópia real separada, espalhe-a com [...original], o que constrói um novo array que você pode alterar por conta própria.
JunoArrays são mantidos por referência Números e strings copiam por valor; arrays copiam por referência, então const b = a lhe dá dois nomes para um array, e mutar através de qualquer um acerta ambos. Essa é a mesma razão pela qual um array const ainda é mutável. Use [...original] para uma cópia independente, tendo em mente que é superficial, o que é exatamente o que você quer para arrays de números e strings.
JunoArrays são mantidos por referência Arrays são objetos mantidos por referência, então a atribuição copia o ponteiro e cada nome vê cada mutação. Isto divide const (fixa a referência) de mutabilidade (altera o alvo), e é o caso de preferir um novo array ao invés de uma mudança no lugar: uma função que muta um array recebido a altera para todos que detêm essa referência. Spread faz uma cópia superficial, independente no nível superior mas compartilhando qualquer array aninhado. Alcance um array quando ordem e contagem importam, e um object quando você quer procurar coisas por chave.

Para onde arrays vão daqui

Um array lhe dá uma lista ordenada sob um único nome: crie um com [], leia-o por índice a partir de zero, altere-o com push e pop, e percorra-o com for...of. A ideia única a levar além deste capítulo é que um array é mantido por referência, então copiar a variável compartilha a lista em vez de duplicá-la.

O próximo passo natural é transformar um array em outro, filtrar uma lista, transformar cada item, adicionar tudo junto, sem escrever o loop à mão. Isso são array methods. Quando você precisa procurar valores por um nome em vez de uma posição, objects são a estrutura para isso.