As duas estruturas de dados mais utilizadas em JavaScript são Object
e Array
.
Os objetos nos permitem criar uma única entidade que armazena itens de dados por chave.
Matrizes nos permitem reunir itens de dados em uma lista ordenada.
No entanto, quando os passamos para uma função, podemos não precisar de tudo isso. A função pode exigir apenas determinados elementos ou propriedades.
A desestruturação de atribuição é uma sintaxe especial que nos permite “descompactar” arrays ou objetos em um monte de variáveis, pois às vezes isso é mais conveniente.
A desestruturação também funciona bem com funções complexas que possuem muitos parâmetros, valores padrão e assim por diante. Em breve veremos isso.
Aqui está um exemplo de como um array é desestruturado em variáveis:
// temos um array com nome e sobrenome deixe arr = ["John", "Smith"] //desestruturando atribuição // define primeiroNome = arr[0] // e sobrenome = arr[1] deixe [primeiro nome, sobrenome] = arr; alerta(primeiroNome); // John alerta(sobrenome); //Smith
Agora podemos trabalhar com variáveis em vez de membros do array.
Parece ótimo quando combinado com split
ou outros métodos de retorno de array:
deixe [primeiroNome, sobrenome] = "John Smith".split(' '); alerta(primeiroNome); // John alerta(sobrenome); //Smith
Como você pode ver, a sintaxe é simples. Existem vários detalhes peculiares. Vamos ver mais exemplos para entender melhor.
“Desestruturar” não significa “destrutivo”.
É chamada de “atribuição de desestruturação”, porque “desestrutura” copiando itens em variáveis. No entanto, a matriz em si não é modificada.
É apenas uma maneira mais curta de escrever:
// deixe [primeiroNome, sobrenome] = arr; deixe primeiroNome = arr[0]; deixe sobrenome = arr[1];
Ignorar elementos usando vírgulas
Elementos indesejados do array também podem ser descartados por meio de uma vírgula extra:
//o segundo elemento não é necessário let [firstName, , title] = ["Júlio", "César", "Cônsul", "da República Romana"]; alerta(título); // Cônsul
No código acima, o segundo elemento do array é ignorado, o terceiro é atribuído a title
e o restante dos itens do array também são ignorados (pois não há variáveis para eles).
Funciona com qualquer iterável no lado direito
…Na verdade, podemos usá-lo com qualquer iterável, não apenas com arrays:
deixe [a, b, c] = "abc"; // ["a", "b", "c"] deixe [um, dois, três] = new Set([1, 2, 3]);
Isso funciona, porque internamente uma atribuição de desestruturação funciona iterando o valor correto. É uma espécie de açúcar de sintaxe para chamar for..of
o valor à direita de =
e atribuir os valores.
Atribuir a qualquer coisa no lado esquerdo
Podemos usar quaisquer “atribuíveis” no lado esquerdo.
Por exemplo, uma propriedade de objeto:
deixe usuário = {}; [usuário.nome, usuário.sobrenome] = "John Smith".split(' '); alerta(usuário.nome); // John alerta(usuário.sobrenome); //Smith
Loop com .entries()
No capítulo anterior, vimos o método Object.entries(obj).
Podemos usá-lo com desestruturação para fazer um loop nas chaves e valores de um objeto:
deixe usuário = { nome: "João", idade: 30 }; // faz um loop sobre as chaves e valores for (deixe [chave, valor] de Object.entries(user)) { alerta(`${chave}:${valor}`); // nome:John, então idade:30 }
O código semelhante para um Map
é mais simples, pois é iterável:
deixe usuário = new Mapa(); user.set("nome", "João"); user.set("idade", "30"); // O mapa itera como pares [chave, valor], muito conveniente para desestruturação for (deixe [chave, valor] do usuário) { alerta(`${chave}:${valor}`); // nome:John, então idade:30 }
Truque de troca de variáveis
Existe um truque bem conhecido para trocar valores de duas variáveis usando uma atribuição de desestruturação:
deixe convidado = "Jane"; deixe admin = "Pete"; // Vamos trocar os valores: make guest=Pete, admin=Jane [convidado, administrador] = [admin, convidado]; alerta(`${convidado} ${admin}`); // Pete Jane (trocado com sucesso!)
Aqui criamos um array temporário de duas variáveis e imediatamente o desestruturamos na ordem trocada.
Podemos trocar mais de duas variáveis desta forma.
Normalmente, se o array for maior que a lista à esquerda, os itens “extras” serão omitidos.
Por exemplo, aqui apenas dois itens são considerados e o resto é simplesmente ignorado:
deixe [nome1, nome2] = ["Júlio", "César", "Cônsul", "da República Romana"]; alerta(nome1); // Júlio alerta(nome2); // César // Outros itens não são atribuídos a lugar nenhum
Se quisermos também reunir tudo o que se segue – podemos adicionar mais um parâmetro que recebe “o resto” usando três pontos "..."
:
deixe [nome1, nome2, ...descansar] = ["Júlio", "César", "Cônsul", "da República Romana"]; // rest é um array de itens, começando pelo terceiro alerta(descanso[0]); // Cônsul alerta(descanso[1]); // da República Romana alerta(rest.comprimento); //2
O valor de rest
é a matriz dos elementos restantes da matriz.
Podemos usar qualquer outro nome de variável no lugar de rest
, apenas certifique-se de que ela tenha três pontos antes e seja a última na tarefa de desestruturação.
deixe [nome1, nome2, ... títulos] = ["Júlio", "César", "Cônsul", "da República Romana"]; // agora títulos = ["Cônsul", "da República Romana"]
Se o array for menor que a lista de variáveis à esquerda, não haverá erros. Valores ausentes são considerados indefinidos:
deixe [nome, sobrenome] = []; alerta(primeiroNome); // indefinido alerta(sobrenome); // indefinido
Se quisermos um valor “padrão” para substituir o valor ausente, podemos fornecê-lo usando =
:
// valores padrão deixe [nome = "Convidado", sobrenome = "Anônimo"] = ["Julius"]; alerta(nome); // Júlio (do array) alerta(sobrenome); //Anônimo (padrão usado)
Os valores padrão podem ser expressões mais complexas ou até mesmo chamadas de função. Eles são avaliados somente se o valor não for fornecido.
Por exemplo, aqui usamos a função prompt
para dois padrões:
//executa apenas o prompt do sobrenome deixe [nome = prompt('nome?'), sobrenome = prompt('sobrenome?')] = ["Julius"]; alerta(nome); // Júlio (do array) alerta(sobrenome); // qualquer prompt obtido
Observação: o prompt
será executado apenas para o valor ausente ( surname
).
A atribuição de desestruturação também funciona com objetos.
A sintaxe básica é:
seja {var1, var2} = {var1:…, var2:…}
Deveríamos ter um objeto existente no lado direito, que queremos dividir em variáveis. O lado esquerdo contém um “padrão” semelhante a um objeto para as propriedades correspondentes. No caso mais simples, é uma lista de nomes de variáveis em {...}
.
Por exemplo:
deixe opções = { título: "Menu", largura: 100, altura: 200 }; deixe {título, largura, altura} = opções; alerta(título); //Cardápio alerta(largura); // 100 alerta(altura); //200
As propriedades options.title
, options.width
e options.height
são atribuídas às variáveis correspondentes.
A ordem não importa. Isso também funciona:
// alterou a ordem em let {...} deixe {altura, largura, título} = {título: "Menu", altura: 200, largura: 100}
O padrão do lado esquerdo pode ser mais complexo e especificar o mapeamento entre propriedades e variáveis.
Se quisermos atribuir uma propriedade a uma variável com outro nome, por exemplo, faça options.width
entrar na variável chamada w
, então podemos definir o nome da variável usando dois pontos:
deixe opções = { título: "Menu", largura: 100, altura: 200 }; // { sourceProperty: targetVariable } deixe {largura: w, altura: h, título} = opções; // largura -> w // altura -> h // título -> título alerta(título); //Cardápio alerta(w); // 100 alerta(h); //200
Os dois pontos mostram “o quê: vai para onde”. No exemplo acima, a propriedade width
vai para w
, a propriedade height
vai para h
e title
é atribuído ao mesmo nome.
Para propriedades potencialmente ausentes, podemos definir valores padrão usando "="
, assim:
deixe opções = { título: "Menu" }; deixe {largura = 100, altura = 200, título} = opções; alerta(título); // Cardápio alerta(largura); // 100 alerta(altura); //200
Assim como acontece com arrays ou parâmetros de função, os valores padrão podem ser quaisquer expressões ou até mesmo chamadas de função. Eles serão avaliados caso o valor não seja fornecido.
No código abaixo, prompt
pede width
, mas não title
:
deixe opções = { título: "Menu" }; deixe {largura = prompt("largura?"), título = prompt("título?")} = opções; alerta(título); // Cardápio alerta(largura); // (qualquer que seja o resultado do prompt)
Também podemos combinar os dois pontos e a igualdade:
deixe opções = { título: "Menu" }; deixe {largura: w = 100, altura: h = 200, título} = opções; alerta(título); //Cardápio alerta(w); // 100 alerta(h); //200
Se tivermos um objeto complexo com muitas propriedades, poderemos extrair apenas o que precisamos:
deixe opções = { título: "Menu", largura: 100, altura: 200 }; // extrai apenas o título como uma variável deixe {título} = opções; alerta(título); //Cardápio
E se o objeto tiver mais propriedades do que variáveis? Podemos pegar um pouco e depois atribuir o “resto” a algum lugar?
Podemos usar o padrão rest, assim como fizemos com arrays. Não é suportado por alguns navegadores mais antigos (ou seja, use Babel para polyfill), mas funciona em navegadores modernos.
Parece assim:
deixe opções = { título: "Menu", altura: 200, largura: 100 }; // título = propriedade chamada título //rest = objeto com o resto das propriedades deixe {título, ...rest} = opções; // agora title="Menu", rest={altura: 200, largura: 100} alerta(rest.altura); //200 alerta(rest.largura); // 100
Te peguei se não há let
Nos exemplos acima as variáveis foram declaradas logo na atribuição: let {…} = {…}
. Claro, poderíamos usar variáveis existentes também, sem let
. Mas há um problema.
Isso não funcionará:
deixe título, largura, altura; //erro nesta linha {título, largura, altura} = {título: "Menu", largura: 200, altura: 100};
O problema é que o JavaScript trata {...}
no fluxo de código principal (não dentro de outra expressão) como um bloco de código. Esses blocos de código podem ser usados para agrupar instruções, como esta:
{ // um bloco de código deixe mensagem = "Olá"; // ... alerta(mensagem); }
Então aqui o JavaScript assume que temos um bloco de código, é por isso que há um erro. Em vez disso, queremos desestruturação.
Para mostrar ao JavaScript que não é um bloco de código, podemos colocar a expressão entre parênteses (...)
:
deixe título, largura, altura; //ok agora ({título, largura, altura} = {título: "Menu", largura: 200, altura: 100}); alerta(título); //Cardápio
Se um objeto ou array contém outros objetos e arrays aninhados, podemos usar padrões mais complexos do lado esquerdo para extrair porções mais profundas.
No código abaixo options
tem outro objeto na propriedade size
e um array na propriedade items
. O padrão no lado esquerdo da tarefa tem a mesma estrutura para extrair valores deles:
deixe opções = { tamanho: { largura: 100, altura: 200 }, itens: ["Bolo", "Donut"], extra: verdadeiro }; // desestrutura a atribuição dividida em múltiplas linhas para maior clareza deixar { size: { //coloque o tamanho aqui largura, altura }, itens: [item1, item2], // atribua itens aqui title = "Menu" // não presente no objeto (o valor padrão é usado) } = opções; alerta(título); // Cardápio alerta(largura); // 100 alerta(altura); //200 alerta(item1); // Bolo alerta(item2); // Rosquinha
Todas as propriedades do objeto de options
, exceto extra
que está ausente na parte esquerda, são atribuídas às variáveis correspondentes:
Finalmente, temos width
, height
, item1
, item2
e title
do valor padrão.
Observe que não há variáveis para size
e items
, pois em vez disso pegamos seu conteúdo.
Há momentos em que uma função possui muitos parâmetros, a maioria dos quais são opcionais. Isso é especialmente verdadeiro para interfaces de usuário. Imagine uma função que cria um menu. Pode ter largura, altura, título, lista de itens e assim por diante.
Esta é uma maneira ruim de escrever tal função:
function showMenu(title = "Sem título", largura = 200, altura = 100, itens = []) { // ... }
Na vida real, o problema é como lembrar a ordem dos argumentos. Normalmente, IDEs tentam nos ajudar, principalmente se o código estiver bem documentado, mas ainda assim… Outro problema é como chamar uma função quando a maioria dos parâmetros estão ok por padrão.
Assim?
// indefinido onde os valores padrão são adequados showMenu("Meu Menu", indefinido, indefinido, ["Item1", "Item2"])
Isso é feio. E torna-se ilegível quando lidamos com mais parâmetros.
A desestruturação vem em socorro!
Podemos passar parâmetros como um objeto, e a função imediatamente os desestrutura em variáveis:
//passamos o objeto para a função deixe opções = { título: "Meu menu", itens: ["Item1", "Item2"] }; // ...e imediatamente expande para variáveis function showMenu({title = "Sem título", largura = 200, altura = 100, itens = []}) { // título, itens – retirados de opções, // largura, altura – padrões usados alerta(`${título} ${largura} ${altura}`); // Meu Menu 200 100 alerta(itens); //Item1, Item2 } mostrarMenu(opções);
Também podemos usar desestruturações mais complexas com objetos aninhados e mapeamentos de dois pontos:
deixe opções = { título: "Meu menu", itens: ["Item1", "Item2"] }; função mostrarMenu({ título = "Sem título", largura: w = 100, // largura vai para w altura: h = 200, // altura vai para h itens: [item1, item2] // o primeiro elemento dos itens vai para o item1, o segundo para o item2 }) { alerta( `${título} ${w} ${h}` ); // Meu Menu 100 200 alerta(item1); //Item1 alerta(item2); //Item2 } mostrarMenu(opções);
A sintaxe completa é a mesma de uma atribuição de desestruturação:
função({ propriedade de entrada: varName = defaultValue ... })
Então, para um objeto de parâmetros, haverá uma variável varName
para a propriedade incomingProperty
, com defaultValue
por padrão.
Observe que tal desestruturação pressupõe que showMenu()
tenha um argumento. Se quisermos todos os valores por padrão, devemos especificar um objeto vazio:
mostrarMenu({}); // ok, todos os valores são padrão mostrarMenu(); // isso daria um erro
Podemos corrigir isso tornando {}
o valor padrão para todo o objeto de parâmetros:
function showMenu({title = "Menu", largura = 100, altura = 200 } = {}) { alerta(`${título} ${largura} ${altura}`); } mostrarMenu(); //Menu 100 200
No código acima, todo o objeto de argumentos é {}
por padrão, então sempre há algo para desestruturar.
A atribuição de desestruturação permite mapear instantaneamente um objeto ou array em muitas variáveis.
A sintaxe completa do objeto:
deixe {prop: varName = defaultValue, ...rest} = objeto
Isso significa que a propriedade prop
deve entrar na variável varName
e, se tal propriedade não existir, o valor default
deverá ser usado.
As propriedades do objeto que não possuem mapeamento são copiadas para o objeto rest
.
A sintaxe completa da matriz:
deixe [item1 = defaultValue, item2, ...rest] = array
O primeiro item vai para item1
; o segundo vai para item2
e todo o resto faz com que o array rest
.
É possível extrair dados de arrays/objetos aninhados, para isso o lado esquerdo deve ter a mesma estrutura que o direito.
importância: 5
Temos um objeto:
deixe usuário = { nome: "João", anos: 30 };
Escreva a tarefa de desestruturação que diz:
propriedade name
na variável name
.
propriedade years
na variável age
.
propriedade isAdmin
na variável isAdmin
(falso, se não houver tal propriedade)
Aqui está um exemplo dos valores após sua tarefa:
deixe usuário = {nome: "John", anos: 30 }; // seu código no lado esquerdo: // ... = usuário alerta(nome); // John alerta(idade); //30 alerta(isAdmin); // falso
deixe usuário = { nome: "João", anos: 30 }; deixe {nome, anos: idade, isAdmin = false} = usuário; alerta(nome); // John alerta(idade); //30 alerta(isAdmin); // falso
importância: 5
Existe um objeto salaries
:
deixe salários = { "João": 100, "Pete": 300, "Maria": 250 };
Crie a função topSalary(salaries)
que retorna o nome da pessoa mais bem paga.
Se salaries
estiverem vazios, deverá retornar null
.
Se houver várias pessoas com os melhores salários, devolva qualquer uma delas.
PS Use Object.entries
e desestruturação para iterar sobre pares chave/valor.
Abra uma sandbox com testes.
function topSalário(salários) { deixe maxSalary = 0; deixe maxName = null; for(const [nome, salário] de Object.entries(salários)) { if (maxSalário <salário) { maxSalário = salário; maxNome = nome; } } return maxNome; }
Abra a solução com testes em uma sandbox.