One place for hosting & domains

      Conjunto

      Entendendo os objetos Map e Conjunto no JavaScript


      O autor selecionou a Open Internet/Free Speech Fund para receber uma doação como parte do programa Write for DOnations.

      No JavaScript, os desenvolvedores geralmente gastam bastante tempo decidindo a estrutura de dados correta a ser usada. Isso acontece porque escolher a estrutura de dados correta pode facilitar a manipulação desses dados mais tarde, economizando tempo e facilitando a compreensão do código. As duas estruturas de dados predominantes para armazenar coleções de dados são Objetos e Matrizes (um tipo de objeto). Os desenvolvedores usam os Obejtos para armazenar pares de chave-valor e Matrizes para armazenar listas indexadas. No entanto, para dar maior flexibilidade aos desenvolvedores, a especificação ECMAScript 2015 introduziu dois novos tipos de objetos iteráveis: Maps, que são coleções ordenadas de pares chave-valor e Conjuntos, que são coleções de valores únicos.

      Neste artigo, você irá analisar os objetos Map e Conjunto, o que os torna semelhantes e diferentes dos Objetos e Matrizes, as propriedades e métodos disponíveis para eles, bem como exemplos de alguns usos práticos.

      Maps

      Um Map é uma coleção de pares chave-valor que pode usar qualquer tipo de dados como uma chave e manter a ordem de suas entradas. Os Maps têm elementos de ambos os Objetos (uma coleção única de pares chave-valor) e Matrizes (uma coleção ordenada). Porém, conceitualmente, eles são mais parecidos aos Objetos. Isso acontece porque, embora o tamanho e a ordem das entradas sejam preservados como em uma Matriz, as entradas – propriamente ditas – são pares chave-valor, como os Objetos.

      Os Maps podem ser inicializados com a sintaxe new Map():

      const map = new Map()
      

      Isso nos dá um Map vazio:

      Output

      Map(0) {}

      Como adicionar valores a um Map

      Você pode adicionar valores a um mapa com o método set(). O primeiro argumento será a chave e o segundo argumento será o valor.

      Isto adiciona três pares chave-valor ao map:

      map.set('firstName', 'Luke')
      map.set('lastName', 'Skywalker')
      map.set('occupation', 'Jedi Knight')
      

      Aqui, começamos a ver como os Maps têm elementos tanto de Objetos como de Matrizes. Assim como acontece com uma Matriz, temos uma coleção com índice zero e podermos ver também quantos itens estão no Map por padrão. Os Maps usam a sintaxe => para representar os pares chave-valor como key => value:

      Output

      Map(3) 0: {"firstName" => "Luke"} 1: {"lastName" => "Skywalker"} 2: {"occupation" => "Jedi Knight"}

      Esse exemplo se assemelha a um objeto regular com chaves baseadas em string, mas podemos usar qualquer tipo de dados como uma chave com os Maps.

      Além de definir manualmente os valores em um Map, podemos também inicializar um Map já com valores. Fazemos isso usando uma Matriz de Matrizes contendo dois elementos, sendo cada um um par chave-valor, que têm a seguinte aparência:

      [ [ 'key1', 'value1'], ['key2', 'value2'] ]
      

      Ao usar a sintaxe a seguir, podemos recriar o mesmo Map:

      const map = new Map([
        ['firstName', 'Luke'],
        ['lastName', 'Skywalker'],
        ['occupation', 'Jedi Knight'],
      ])
      

      Nota: este exemplo usa as vírgulas à direita, também conhecidas como vírgulas pendentes. Trata-se de uma prática de formatação do JavaScript na qual o item final de uma série – ao se declarar uma coleção de dados – tem uma vírgula no final. Embora essa escolha de formatação possa ser usada para obter diferenças de limpeza e uma manipulação de código mais fácil, usar isso ou não é uma questão de preferência. Para obter mais informações sobre as vírgulas à direita, consulte este artigo de Vírgulas à direita dos documentos Web da MDN (Mozilla Developer Network).

      A propósito, essa sintaxe é a mesma do resultado da chamada Object.entries() em um Objeto. Isso proporciona uma maneira pronta de converter um Objeto em um Map, como mostrado no bloco de código a seguir:

      const luke = {
        firstName: 'Luke',
        lastName: 'Skywalker',
        occupation: 'Jedi Knight',
      }
      
      const map = new Map(Object.entries(luke))
      

      Como alternativa, você pode transformar um Map de volta em um Objeto ou uma Matriz com uma única linha de código.

      O seguinte converte um Map em um Objeto:

      const obj = Object.fromEntries(map)
      

      Isso resultará no seguinte valor de obj:

      Output

      {firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

      Agora, vamos converter um Map em uma Matriz:

      const arr = Array.from(map)
      

      Isso resultará na seguinte Matriz para arr:

      Output

      [ ['firstName', 'Luke'], ['lastName', 'Skywalker'], ['occupation', 'Jedi Knight'] ]

      Chaves de Map

      Os Maps aceitam qualquer tipo de dados como uma chave e não permitem valores de chave duplicados. Podemos demonstrar isso criando um mapa e usando valores não string como chaves, além de definir dois valores para a mesma chave.

      Primeiro, vamos inicializar um mapa com chaves não string:

      const map = new Map()
      
      map.set('1', 'String one')
      map.set(1, 'This will be overwritten')
      map.set(1, 'Number one')
      map.set(true, 'A Boolean')
      

      Esse exemplo irá sobrepor a primeira chave de 1 com a seguinte e tratará a string '1' e o número 1 como chaves únicas:

      Output

      0: {"1" => "String one"} 1: {1 => "Number one"} 2: {true => "A Boolean"}

      Embora acredite-se que um Objeto regular JavaScript já possa lidar com os Números, os booleanos e outros tipos de dados primitivos como chaves, isso não é, de fato, o caso, já que Objetos transformam todas as chaves em strings.

      Como exemplo, inicialize um objeto com uma chave numérica e compare o valor para a chave numérica 1 e a chave em string "1":

      // Initialize an object with a numerical key
      const obj = { 1: 'One' }
      
      // The key is actually a string
      obj[1] === obj['1']  // true
      

      É por isso que, se você tentar usar um Objeto como uma chave, ele imprimirá a string object Object em vez disso.

      Como exemplo, crie um Objeto e, em seguida, use-o como chave de outro Objeto:

      // Create an object
      const objAsKey = { foo: 'bar' }
      
      // Use this object as the key of another object
      const obj = {
        [objAsKey]: 'What will happen?'
      }
      

      Isso irá resultar no seguinte:

      Output

      {[object Object]: "What will happen?"}

      Isso não acontece com o Map. Tente criar um Objeto e configurá-lo como a chave de um Map:

      // Create an object
      const objAsKey = { foo: 'bar' }
      
      const map = new Map()
      
      // Set this object as the key of a Map
      map.set(objAsKey, 'What will happen?')
      

      Agora, a key do elemento Map é o objeto que criamos.

      Output

      key: {foo: "bar"} value: "What will happen?"

      Existe uma coisa importante a se observar sobre o uso de um Objeto ou Matriz como uma chave: o Map está usando a referência ao Objeto para comparar a igualdade, não o valor literal do Objeto. No JavaScript {} === {} retorna false, uma vez que os dois Objetos não são os mesmos dois Objetos, apesar de terem o mesmo valor (vazio).

      Isso significa que adicionar dois Objetos únicos com o mesmo valor criará um Map com duas entradas:

      // Add two unique but similar objects as keys to a Map
      map.set({}, 'One')
      map.set({}, 'Two')
      

      Isso irá resultar no seguinte:

      Output

      Map(2) {{…} => "One", {…} => "Two"}

      No entanto, usar a mesma referência de Objeto duas vezes criará um Map com uma entrada.

      // Add the same exact object twice as keys to a Map
      const obj = {}
      
      map.set(obj, 'One')
      map.set(obj, 'Two')
      

      O que dará como resultado o seguinte:

      Output

      Map(1) {{…} => "Two"}

      O segundo set() está atualizando exatamente a mesma chave que a primeira, de modo que acabamos com um Map que possui apenas um valor.

      Obtendo e excluindo itens de um Map

      Uma das desvantagens de se trabalhar com Objetos é que pode ser difícil enumerá-los, ou trabalhar com todos as chaves ou valores. A estrutura do Map, ao contrário, possui muitas propriedades embutidas que tornam mais direto o trabalho com seus elementos.

      Podemos inicializar um novo Map para demonstrar os métodos e propriedades a seguir: delete(), has(), get() e size.

      // Initialize a new Map
      const map = new Map([
        ['animal', 'otter'],
        ['shape', 'triangle'],
        ['city', 'New York'],
        ['country', 'Bulgaria'],
      ])
      

      Use o método has() para verificar se existe um item em um mapa. O has() retornará um Booleano.

      // Check if a key exists in a Map
      map.has('shark') // false
      map.has('country') // true
      

      Use o método get() para recuperar um valor pela chave.

      // Get an item from a Map
      map.get('animal') // "otter"
      

      Um benefício em particular que os Maps têm em relação aos Objetos é que você pode descobrir o tamanho de um Map a qualquer momento, assim como com uma Matriz. É possível obter a contagem de itens em um Map com a propriedade size (tamanho). Isso envolve menos passos do que converter um Objeto em uma Matriz para descobrir seu tamanho.

      // Get the count of items in a Map
      map.size // 4
      

      Use o método delete() para remover um item de um Map pela chave. O método retornará um Booleano — true se já houver um item e tiver sido excluído e retornará um false se não corresponder a nenhum item.

      // Delete an item from a Map by key
      map.delete('city') // true
      

      Isso resulta no seguinte Map:

      Output

      Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

      Por fim, é possível limpar todos os valores do Map com o método map.clear().

      // Empty a Map
      map.clear()
      

      Isso produzirá o seguinte resultado:

      Output

      Map(0) {}

      Chaves, valores e entradas para Maps

      Os Objetos podem recuperar chaves, valores e entradas, usando as propriedades do construtor Object. Os Maps, por outro lado, têm métodos protótipos que nos permitem obter diretamente as chaves, valores e entradas da instância Map.

      Os métodos keys(), values() e entries() retornam todos um MapIterator, que se assemelha a uma Matriz na qual você pode usar for...of executar o loop nos valores.

      Apresentamos a seguir outro exemplo de um Map, que podemos usar para demonstrar esses métodos:

      const map = new Map([
        [1970, 'bell bottoms'],
        [1980, 'leg warmers'],
        [1990, 'flannel'],
      ])
      

      O método keys() retorna as chaves:

      map.keys()
      

      Output

      MapIterator {1970, 1980, 1990}

      O método values() retorna os valores:

      map.values()
      

      Output

      MapIterator {"bell bottoms", "leg warmers", "flannel"}

      O método entries() retorna uma matriz de pares chave-valor:

      map.entries()
      

      Output

      MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

      Iteração com Map

      O Map tem um método integrado forEach, similar a uma Matriz, para iteração integrada. No entanto, existe um pouco de diferença quanto ao que eles iteram. O callback (retorno de chamada) do forEach de um Map itera value, key e map, propriamente dito, ao passo que a versão Matriz itera item, index e array, propriamente dito.

      // Map
      Map.prototype.forEach((value, key, map) = () => {})
      
      // Array
      Array.prototype.forEach((item, index, array) = () => {})
      

      Trata-se de uma grande vantagem dos Maps em relação aos Objetos, uma vez que os Objetos precisam ser convertidos com keys(), values(), ou entries() e não há uma maneira simples de recuperar as propriedades de um Objeto sem convertê-lo.

      Para demonstrar isso, vamos iterar nosso Map e registrar os pares chave-valor no console:

      // Log the keys and values of the Map with forEach
      map.forEach((value, key) => {
        console.log(`${key}: ${value}`)
      })
      

      Isso dará:

      Output

      1970: bell bottoms 1980: leg warmers 1990: flannel

      Como um loop for...of itera items iteráveis como Maps e Matrizes, podemos obter exatamente o mesmo resultado por meio da desestruturação da matriz dos itens do Map:

      // Destructure the key and value out of the Map item
      for (const [key, value] of map) {
        // Log the keys and values of the Map with for...of
        console.log(`${key}: ${value}`)
      }
      

      Propriedades e métodos de Map

      A tabela a seguir mostra uma lista de propriedades e métodos de Map, para referência rápida:

      Propriedades/Métodos Descrição Retorna
      set(key, value) Adiciona um par chave-valor a um Map Objeto Map
      delete(key) Remove um par chave-valor de um Map por chave Booleano
      get(key) Retorna um valor por meio da chave valor
      has(key) Verifica quanto à presença de um elemento em um Map por chave Booleano
      clear() Remove todos os itens de um Map N/A
      keys() Retorna todas as chaves em um Map Objeto MapIterator
      values() Retorna todas os valores em um Map Objeto MapIterator
      entries() Retorna todas as chaves e valores em um Map como [key, value] Objeto MapIterator
      forEach() Itera no Map por ordem de inserção N/A
      size Retorna o número de itens em um Map Número

      Quando usar um Map

      Em suma, os Maps são semelhantes aos Objetos no sentido de que têm pares de chave-valor, mas os Maps têm diversas vantagens em relação aos objetos:

      • Tamanho – Os Maps têm uma propriedade size, ao passo que os Objetos não têm uma maneira integrada de descobrir seu tamanho.
      • Iteração – Os Maps podem ser iterados diretamente, ao passo que os Objetos não.
      • Flexibilidade – Os Maps podem ter qualquer tipo de dados (primitivo ou Objeto) como a chave para um valor, enquanto Objetos admitem apenas strings.
      • Ordenados – Os Maps retêm sua ordem de inserção, ao passo que os objetos não têm uma ordem garantida.

      Devido a esses fatores, os Maps são uma estrutura de dados avançada a se considerar. No entanto, os Objetos têm também algumas vantagens importantes:

      • JSON – Os Objetos funcionam de maneira impecável com JSON.parse() e JSON.stringify(), duas funções essenciais para se trabalhar com o JSON, um formato de dados comum com os quais muitas APIs REST lidam.
      • Trabalhar com um único elemento – Ao trabalhar com um valor conhecido em um Objeto, você pode acessá-lo diretamente com a chave, sem a necessidade de usar um método, como o get() do Map.

      Essa lista ajudará você a decidir se um Map ou um Objeto é a estrutura de dados correta para o seu caso de uso.

      Conjunto

      Um Conjunto é uma coleção de valores únicos. Ao contrário de um Map, um Conjunto é conceitualmente mais parecido com uma Matriz do que um com um Objeto, já que é uma lista de valores e não pares chave-valor. No entanto, o Conjunto não é um substituto da Matriz, mas sim um suplemento que provém suporte adicional para se trabalhar com dados duplicados.

      Você pode inicializar os Conjuntos com a sintaxe new Set().

      const set = new Set()
      

      Isso nos dá um Conjunto vazio:

      Output

      Set(0) {}

      Os itens podem ser adicionados a um Conjunto com o método add(). (Isso não deve ser confundido com o método set() disponível para Map, embora sejam semelhantes.)

      // Add items to a Set
      set.add('Beethoven')
      set.add('Mozart')
      set.add('Chopin')
      

      Como os Conjuntos podem conter apenas valores únicos, qualquer tentativa de adicionar um valor que já exista será ignorada.

      set.add('Chopin') // Set will still contain 3 unique values
      

      Nota: a mesma comparação de igualdade que se aplica às chaves do Map se aplica aos itens do Conjunto. Dois objetos que têm o mesmo valor, mas não compartilham a mesma referência não serão considerados iguais.

      Também é possível inicializar os Conjuntos com uma Matriz de valores. Se houver valores duplicados na matriz, eles serão removidos do Conjunto.

      // Initialize a Set from an Array
      const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])
      

      Output

      Set(3) {"Beethoven", "Mozart", "Chopin"}

      Por outro lado, um Conjunto pode ser convertido em uma Matriz com apenas uma linha de código:

      const arr = [...set]
      

      Output

      (3) ["Beethoven", "Mozart", "Chopin"]

      O Conjunto possui muitos dos mesmos métodos e propriedades do Mapa, incluindo delete(), has(), clear() e size.

      // Delete an item
      set.delete('Beethoven') // true
      
      // Check for the existence of an item
      set.has('Beethoven') // false
      
      // Clear a Set
      set.clear()
      
      // Check the size of a Set
      set.size // 0
      

      Observe que o Conjunto não tem um modo de acessar um valor por uma chave ou índice, como Map.get(key) ou arr[index].

      Chaves, valores e entradas para Conjuntos

      Tanto o Map quanto o Conjunto possui os métodos keys(), values() e entries() que retornam um Iterator. No entanto, embora cada um desses métodos tenha um objetivo distinto no Map, os Conjuntos não têm chaves e, portanto, chaves são um alias para os valores. Isso significa que os métodos keys() e values() retornarão o mesmo Iterator e as entries() retornarão o valor duas vezes. Assim, faz todo sentido usar apenas values() com Conjuntos, já que existem outros dois métodos para obter consistência e compatibilidade cruzada com o Map.

      const set = new Set([1, 2, 3])
      // Get the values of a set
      set.values()
      

      Output

      SetIterator {1, 2, 3}

      Iteração com Conjunto

      Assim como o Map, o Conjunto possui o método embutido forEach(). Como os Conjuntos não têm chaves, o primeiro e segundo parâmetros do callback do forEach() retornam o mesmo valor, de modo que não existe um caso de uso para ele além da compatibilidade com o Map. Os parâmetros do forEach() são (value, key, set).

      Tanto forEach() quanto for...of podem ser usado no Conjunto. Primeiro, vamos examinar a iteração forEach():

      const set = new Set(['hi', 'hello', 'good day'])
      
      // Iterate a Set with forEach
      set.forEach((value) => console.log(value))
      

      Depois, podemos escrever a versão for...of:

      // Iterate a Set with for...of
      for (const value of set) {  
          console.log(value);
      }
      

      Ambas essas estratégias resultarão no seguinte:

      Output

      hi hello good day

      Propriedades e métodos do Conjunto

      A tabela a seguir mostra uma lista de propriedades e métodos do Conjunto para referência rápida:

      Propriedades/Métodos Descrição Retorna
      add(value) Adiciona um novo item em um Conjunto Objeto Set
      delete(value) Remove o item especificado de um Conjunto Booleano
      has() Verifica quanto à presença de um item em um Conjunto Booleano
      clear() Remove todos os itens de um Conjunto N/A
      keys() Retorna todos os valores de um Conjunto (o mesmo que values()) Objeto SetIterator
      values() Retorna todos os valores de um Conjunto (o mesmo que keys()) Objeto SetIterator
      entries() Retorna todos os valores de um Conjunto como [value, value] Objeto SetIterator
      forEach() Itera no Conjunto por ordem de inserção N/A
      size Retorna o número de itens em um Conjunto Número

      Quando usar um Conjunto

      O Conjunto é um complemento útil ao seu conjunto de ferramentas do JavaScript, especialmente para trabalhar com valores duplicados de dados.

      Numa única linha, podemos criar uma nova Matriz, sem duplicar valores de uma Matriz que possua valores duplicados.

      const uniqueArray = [ ...new Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]
      

      Isso dará:

      Output

      (3) [1, 2, 3]

      O Conjunto pode ser usado para encontrar a união, a intersecção e a diferença entre dois conjuntos de dados. No entanto, as Matrizes têm uma vantagem significativa em relação aos Conjuntos para manipulação adicional dos dados, devidos aos métodos sort(), map(), filter() e reduce(), bem como uma compatibilidade direta com os métodos JSON.

      Conclusão

      Neste artigo, você aprendeu que um Map é uma coleção de pares chave-valor ordenados e que um Conjunto é uma coleção de valores únicos. Ambas essas estruturas de dados contribuem com capacidades adicionais ao JavaScript e simplificam tarefas comuns, como descobrir o tamanho de uma coleção de pares chave-valor e remover itens duplicados de um conjunto de dados, respectivamente. Por outro lado, Objetos e Matrizes vem sendo tradicionalmente usados para o armazenamento e manipulação de dados no JavaScript. Eles têm compatibilidade direta com o JSON, fazendo deles as estruturas de dados mais essenciais, especialmente para trabalhar com as APIs REST. Os Maps e Conjuntos são úteis principalmente como estruturas de dados que auxiliam os Objetos e as Matrizes.

      Se quiser aprender mais sobre o JavaScript, confira a página inicial de nossa série sobre Como programar em JavaScript, ou pesquise nossa série sobre Como programar em Node.js para obter artigos sobre o desenvolvimento de back-end.



      Source link