One place for hosting & domains

      Entendendo

      Entendendo os parâmetros predefinidos em JavaScript


      O autor selecionou a COVID-19 Relief Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

      Introdução

      No ECMAScript 2015, os parâmetros predefinidos de funções introduzidos na linguagem JavaScript. Eles permitem que os desenvolvedores inicializem uma função com valores predefinidos, caso os argumentos não sejam fornecidos à chamada da função. Inicializar parâmetros de função desta maneira tornará a leitura delas mais fácil e menos propensa a erros. Além disso, isso providencia um comportamento padrão para elas Isso ajudará você a evitar erros decorrentes da passagem de argumentos undefined (não definidos) e desestruturação de objetos que não existem.

      Neste artigo, você irá revisar a diferença entre parâmetros e argumentos, aprender como usar os parâmetros predefinidos em funções, ver maneiras alternativas para dar suporte aos parâmetros predefinidos, além de aprender quais tipos de valores e expressões podem ser usados como parâmetros predefinidos. Você também verá exemplos que demonstram como os parâmetros predefinidos funcionam em JavaScript.

      Argumentos e parâmetros

      Antes de explicar os parâmetros predefinidos de funções, é importante saber o que esses parâmetros podem usar como predefinição. Por isso, vamos primeiro analisar a diferença entre argumentos e parâmetros em uma função. Se quiser aprender mais sobre essa distinção, verifique nosso artigo anterior da série JavaScript, Como definir funções em JavaScript.

      No bloco de código seguinte, você criará uma função que retorna o cubo de um número determinado, definido como x:

      // Define a function to cube a number
      function cube(x) {
        return x * x * x
      }
      

      A variável x neste exemplo é um parâmetro — uma variável com nome passada para uma função. Um parâmetro deve estar sempre contido em uma variável e nunca deve possuir um valor direto.

      Agora, dê uma olhada neste próximo bloco de código, que chama a função cube (cubo) que você acabou de criar:

      // Invoke cube function
      cube(10)
      

      Isso dará o seguinte resultado:

      Output

      1000

      Neste caso, 10 é um argumento: um valor passado para uma função quando ela é invocada. Muitas vezes, o valor também estará contido em uma variável, como neste próximo exemplo:

      // Assign a number to a variable
      const number = 10
      
      // Invoke cube function
      cube(number)
      

      Isso gerará o mesmo resultado:

      Output

      1000

      Se você não passar um argumento para uma função que espere um, a função utilizará implicitamente undefined como o valor:

      // Invoke the cube function without passing an argument
      cube()
      

      Isso retornará:

      Output

      NaN

      Neste caso, cube() está tentando calcular o valor de undefined * undefined * undefined, que resulta em NaN, ou “not a number” (não é um número). Para obter mais informações sobre isso, consulte a seção de números do Entendendo tipos de dados em JavaScript.

      Às vezes, esse comportamento automático pode causar problemas. Em alguns casos, você pode querer que o parâmetro tenha um valor, mesmo se nenhum argumento tiver sido passado para a função. São nesse casos em que a funcionalidade de parâmetros predefinidos vem a calhar, um tópico que será abordado na próxima seção.

      Sintaxe dos parâmetros predefinidos

      Com a adição dos parâmetros predefinidos no ES2015, você pode agora atribuir um valor predefinido para qualquer parâmetro. A função utilizará esse valor, ao invés de undefined quando chamada sem um argumento. Esta seção mostrará primeiro como fazer isso manualmente. Em seguida, irá guiar você na definição de parâmetros predefinidos.

      Sem os parâmetros predefinidos, você teria que verificar explicitamente se há valores undefined para definir os padrões, como mostrado neste exemplo:

      // Check for undefined manually
      function cube(x) {
        if (typeof x === 'undefined') {
          x = 5
        }
      
        return x * x * x
      }
      
      cube()
      

      Ele utiliza uma instrução condicional para verificar se o valor foi provisionado automaticamente como undefined. Em seguida, define o valor de x como 5. Isso resultará no seguinte:

      Output

      125

      Em contrapartida, usar os parâmetros predefinidos atinge o mesmo objetivo com um código muito menor. Defina um valor predefinido para o parâmetro em cube, atribuindo-lhe o operador de atribuição de igualdade (=), como destacado aqui:

      // Define a cube function with a default value
      function cube(x = 5) {
        return x * x * x
      }
      

      Agora, quando a função cube for invocada sem um argumento, ela atribuirá 5 a x e retornará o cálculo ao invés de NaN:

      // Invoke cube function without an argument
      cube()
      

      Output

      125

      Ela ainda funcionará como previsto quando um argumento for passado, ignorando o valor predefinido:

      // Invoke cube function with an argument
      cube(2)
      

      Output

      8

      No entanto, um ponto negativo importante a se considerar é que o valor predefinido do parâmetro também substituirá um undefined explicitamente passado como um argumento para uma função, como demonstrado aqui:

      // Invoke cube function with undefined
      cube(undefined)
      

      Isso resultará no cálculo com x igual a 5:

      Output

      125

      Neste caso, os valores predefinidos dos parâmetros foram calculados, e um valor undefined explícito não os substituíram.

      Agora que você tem uma ideia da sintaxe básica dos parâmetros predefinidos, a próxima seção mostrará como os parâmetros predefinidos funcionam com diferentes tipos de dados.

      Tipos de dados dos parâmetros predefinidos

      Qualquer valor primitivo ou objeto pode ser usado como um valor predefinido do parâmetro. Nesta seção, você verá como essa flexibilidade aumenta as maneiras como os parâmetros predefinidos podem ser usados.

      Primeiro, defina os parâmetros usando um número, string, booleano, objeto, matriz e valor nulo como um valor predefinido. Este exemplo utilizará a sintaxe arrow function:

      // Create functions with a default value for each data type
      const defaultNumber = (number = 42) => console.log(number)
      const defaultString = (string = 'Shark') => console.log(string)
      const defaultBoolean = (boolean = true) => console.log(boolean)
      const defaultObject = (object = { id: 7 }) => console.log(object)
      const defaultArray = (array = [1, 2, 3]) => console.log(array)
      const defaultNull = (nullValue = null) => console.log(nullValue)
      

      Quando essas funções forem invocadas sem parâmetros, elas usarão todos os valores predefinidos:

      // Invoke each function
      defaultNumber()
      defaultString()
      defaultBoolean()
      defaultObject()
      defaultArray()
      defaultNull()
      

      Output

      42 "Shark" true {id: 7} (3) [1, 2, 3] null

      Note que qualquer objeto criado em um parâmetro predefinido será criado sempre que a função for chamada. Um dos casos de uso comum para os parâmetros predefinidos é o uso deste comportamento para obter valores de um objeto. Se você tentar desestruturar ou acessar um valor de um objeto que não existe, ele emitirá um erro. No entanto, se o parâmetro predefinido for um objeto vazio, ele simplesmente retornará valores undefined, ao invés de emitir um erro:

      // Define a settings function with a default object
      function settings(options = {}) {
        const { theme, debug } = options
      
        // Do something with settings
      }
      

      Isso evitará erros causados pela desestruturação de objetos que não existem.

      Agora que você viu como os parâmetros predefinidos operam com diferentes tipos de dados, a próxima seção explicará como vários parâmetros predefinidos podem funcionar em conjunto.

      Usando multiplos parâmetros predefinidos

      Use quantos parâmetros predefinidos quiser em uma função. Esta seção mostrará como fazê-lo e como usar isso para manipular o DOM em um exemplo real.

      Primeiro, declare uma função sum() com múltiplos parâmetros predefinidos:

      // Define a function to add two values
      function sum(a = 1, b = 2) {
        return a + b
      }
      
      sum()
      

      Isso resultará no cálculo predefinido a seguir:

      Output

      3

      Além disso, o valor usado em um parâmetro pode ser usado em qualquer parâmetro predefinido subsequente, da esquerda para a direita. Por exemplo, esta função createUser cria um objeto de usuário userObj como sendo o terceiro parâmetro e tudo o que a função faz é retornar userObj com os dois primeiros parâmetros:

      // Define a function to create a user object using parameters
      function createUser(name, rank, userObj = { name, rank }) {
        return userObj
      }
      
      // Create user
      const user = createUser('Jean-Luc Picard', 'Captain')
      

      Se você chamar o user aqui, você receberá o seguinte:

      Output

      {name: "Jean-Luc Picard", rank: "Captain"}

      Geralmente, é recomendável colocar todos os parâmetros predefinidos no final de uma lista de parâmetros, para que você possa deixar de fora valores opcionais facilmente. Se você usar um parâmetro predefinido primeiro, você terá que passar explicitamente undefined para usar o valor predefinido.

      Aqui está um exemplo com o parâmetro predefinido no início da lista:

      // Define a function with a default parameter at the start of the list
      function defaultFirst(a = 1, b) {
        return a + b
      }
      

      Ao chamar esta função, você teria que chamar o defaultFirst() com dois argumentos:

      defaultFirst(undefined, 2)
      

      Isso resultaria no seguinte:

      Output

      3

      Aqui está um exemplo com o parâmetro predefinido no final da lista:

      // Define a function with a default parameter at the end of the list
      function defaultLast(a, b = 1) {
        return a + b
      }
      
      defaultLast(2)
      

      Isso resultaria no mesmo valor:

      Output

      3

      Ambas as funções têm o mesmo resultado, mas aquela com o valor predefinido por último resulta em uma chamada de função muito mais limpa.

      Para um exemplo real, aqui está uma função que criará um elemento DOM e adicionará um rótulo de texto e classes, caso existam.

      // Define function to create an element
      function createNewElement(tag, text, classNames = []) {
        const el = document.createElement(tag)
        el.textContent = text
      
        classNames.forEach(className => {
          el.classList.add(className)
        })
      
        return el
      }
      

      Chame a função com algumas classes em uma matriz:

      const greeting = createNewElement('p', 'Hello!', ['greeting', 'active'])
      

      Chamar greeting resultará no valor a seguir:

      Output

      <p class="greeting active">Hello!</p>

      No entanto, se você deixar a matriz classNames fora da chamada da função, ela ainda funcionará.

      const greeting2 = createNewElement('p', 'Hello!')
      

      greeting2 tem agora o valor a seguir:

      Output

      <p>Hello!</p>

      Neste exemplo, o forEach() pode ser usado em uma matriz vazia sem problemas. Se aquela matriz vazia não fosse definida no parâmetro predefinido, você receberia o erro a seguir:

      Output

      VM2673:5 Uncaught TypeError: Cannot read property 'forEach' of undefined at createNewElement (<anonymous>:5:14) at <anonymous>:12:18

      Agora que você viu como multiplos parâmetros predefinidos podem interagir, continue para a próxima seção para ver como as chamadas de função funcionam como parâmetros predefinidos.

      Chamadas de função como parâmetros predefinidos

      Além dos primitivos e objetos, o resultado de chamar uma função pode ser usado como parâmetro predefinido.

      Neste bloco de código, você criará uma função para retornar um número aleatório e, em seguida, usará o resultado como o valor predefinido de parâmetro em uma função cube:

      // Define a function to return a random number from 1 to 10
      function getRandomNumber() {
        return Math.floor(Math.random() * 10)
      }
      
      // Use the random number function as a default parameter for the cube function
      function cube(x = getRandomNumber()) {
        return x * x * x
      }
      

      Agora, invocar a função cube sem um parâmetro vai gerar resultados potencialmente diferentes sempre que você chamá-la:

      // Invoke cube function twice for two potentially different results
      cube()
      cube()
      

      Os resultados destas chamadas de função variará:

      Output

      512 64

      Você pode até mesmo usar métodos integrados, como aqueles presentes no objeto Math, e usar o valor retornado em uma chamada de função como um parâmetro em outra função.

      No exemplo a seguir, um número aleatório é atribuído a x, que é usado como parâmetro na função cube que você criou. Em seguida, o parâmetro y calculará a raiz cúbica do número e verificará para ver se x e y são iguais:

      // Assign a random number to x
      // Assign the cube root of the result of the cube function and x to y
      function doesXEqualY(x = getRandomNumber(), y = Math.cbrt(cube(x))) {
        return x === y
      }
      
      doesXEqualY()
      

      Isso resultará no seguinte:

      Output

      true

      Um parâmetro predefinido pode até ser uma definição de função, como visto neste exemplo, que define um parâmetro como a função inner (interna) e retorna a chamada de função de parameter:

      // Define a function with a default parameter that is an anonymous function
      function outer(
        parameter = function inner() {
          return 100
        }
      ) {
        return parameter()
      }
      
      // Invoke outer function
      outer()
      

      Output

      100

      Essa função inner será criada a partir do zero toda vez que a função outer (externa) for invocada.

      Conclusão

      Neste artigo, você aprendeu o que são os parâmetros predefinidos de função e como usá-los. Agora, você pode usar os parâmetros predefinidos para ajudar a manter suas funções limpas e fáceis de ler. Você também pode atribuir objetos vazios e matrizes a parâmetros logo no início. Isso reduz a complexidade e o número de linhas de código ao lidar com situações como a recuperação de valores de um objeto ou a execução de um loop em uma matriz.

      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

      Entendendo Generators no JavaScript


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

      Introdução

      No ECMAScript 2015, os generators (geradores) foram introduzidos na linguagem JavaScript. Um gerador é um processo que pode ser pausado e retomado e gerar vários valores. Um gerador no JavaScript consiste em uma função gerador, que retorna um objeto iterável Generator.

      Geradores podem manter o estado, fornecendo uma maneira eficiente de fazer iteradores, e são capazes de lidar com fluxos de dados infinitos, o que pode ser usado para implementar a rolagem infinita no front-end de um aplicativo Web para operar em dados de onda sonora, dentre outros. Além disso, quando usado com Promises, os geradores podem imitar a funcionalidade async/await, que nos permite lidar com código assíncrono de uma maneira mais simples e legível. Embora o async/await seja uma maneira mais difundida para lidar com casos de uso comuns, para alguns casos de uso assíncronos simples, como a obtenção de dados de uma API, vale a pena aprender a usar os recursos mais avançados dos geradores.

      Neste artigo, vamos abordar como criar funções geradoras, como iterar sobre objetos Generator, a diferença entre o yield (rendimento) e return (retorno) dentro de um gerador e outros aspectos de trabalho acerca de geradores.

      Funções geradoras

      Uma função geradora é uma função que retorna um objeto Generator e é definida pela palavra-chave function seguida de um asterisco (*), como mostrado a seguir:

      // Generator function declaration
      function* generatorFunction() {}
      

      De vez em quando, você verá o asterisco próximo ao nome da função, ao invés da palavra-chave function, tal como function *generatorFunction(). Isso funciona da mesma forma, mas function* é uma sintaxe mais amplamente aceita.

      Funções geradoras também podem ser definidas em uma expressão, como funções regulares:

      // Generator function expression
      const generatorFunction = function*() {}
      

      Geradores podem até ser os métodos de um objeto ou classe:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      }
      
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}
      }
      

      Os exemplos ao longo deste artigo usarão a sintaxe de declaração da função geradora.

      Nota: diferente de funções regulares, os geradores não podem ser construídos com a palavra-chave new, nem podem ser usados em conjunto com funções de seta.

      Agora que sabe como declarar funções geradoras, vamos ver os objetos iteráveis Generator que elas retornam.

      Objetos Generator

      Tradicionalmente, funções em JavaScript são executadas até o fim, e chamar uma função retornará um valor ao chegar na palavra-chave de return. Se a palavra-chave return for omitida, uma função retornará implicitamente undefined.

      No código a seguir, por exemplo, declaramos uma função sum() que retorna um valor que é a soma de dois argumentos inteiros:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b
      }
      

      Chamar a função retorna um valor que é a soma dos argumentos:

      const value = sum(5, 6) // 11
      

      No entanto, uma função geradora não retorna um valor imediatamente e, ao invés disso, retorna um objeto iterável Generator. No exemplo a seguir, declaramos uma função e demos a ela um único valor de retorno, assim como uma função padrão:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'
      }
      

      Quando invocarmos a função gerador, ela retornará o objeto Generator, que podemos atribuir a uma variável:

      // Assign the Generator object to generator
      const generator = generatorFunction()
      

      Se essa função fosse uma função regular, esperaríamos que o generator nos desse a string que é retornada na função. No entanto, o que realmente recebemos é um objeto em um estado suspended (suspenso). Assim, chamar generator dará um resultado semelhante ao seguinte:

      Output

      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      O objeto Generator retornado pela função é um iterador. Um iterador é um objeto que tem um método next() disponível, que é usado para iterar através de uma sequência de valores. O método next() retorna um objeto com as propriedades value e done. value representa o valor retornado e done indica se o iterador percorreu todos os seus valores ou não.

      Sabendo disso, vamos chamar o next() no nosso generator e obter o valor atual e o estado do iterador:

      // Call the next method on the Generator object
      generator.next()
      

      Isso dará o seguinte resultado:

      Output

      {value: "Hello, Generator!", done: true}

      O valor retornado da chamada next() é Hello, Generator! e o estado done é true (verdade), uma vez que este valor veio de um return que fechou o iterador. Como o iterador finalizou seu processo, o status da função geradora mudará de suspended para closed. Chamar generator novamente dará o seguinte:

      Output

      generatorFunction {<closed>}

      Até agora, demostramos apenas como uma função geradora pode ser uma maneira mais complexa para obter o valor return de uma função. Mas funções geradoras também têm recursos únicos que as distinguem de funções normais. Na seção seguinte, vamos aprender sobre o operador yield e ver como um generator pode ser pausado e retomar a execução.

      Operadores yield

      Os generators introduzem uma palavra-chave nova ao JavaScript: yield. O yield pode pausar uma função geradora e retornar o valor que vem após o yield, fornecendo uma maneira leve para iterar valores.

      Neste exemplo, vamos pausar a função geradora três vezes com valores diferentes e retornaremos um valor no final. Então, vamos atribuir nosso objeto Generator à variável generator.

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      
        return 'The Oracle'
      }
      
      const generator = generatorFunction()
      

      Agora, quando chamarmos o next() na função geradora, ela pausará toda vez que encontrar o yield. O done será configurado para false após cada yield, indicando que o generator não foi finalizado. Assim que encontrar um return, ou não houver mais yields encontrados na função, done mudará para true e o generator terminará.

      Use o método next() quatro vezes, um após a outro:

      // Call next four times
      generator.next()
      generator.next()
      generator.next()
      generator.next()
      

      Eles darão as seguintes quatro linhas de saída, por ordem:

      Output

      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Note que um gerador não exige um return; se omitida, a última iteração retornará {value: undefined, done: true}, assim como qualquer chamada subsequente a next() após um gerador ter sido concluído.

      Iterando sobre um gerador

      Ao usar o método next(), iteramos manualmente pelo objeto Generator, recebendo todas as propriedades value e done do objeto completo. No entanto, assim como o Array, Map e Set (Matriz, mapa e conjunto), um Generator segue o protocolo de iteração e pode ser iterado com o for...of:

      // Iterate over Generator object
      for (const value of generator) {
        console.log(value)
      }
      

      Isso retornará o seguinte:

      Output

      Neo Morpheus Trinity

      O operador de propagação também pode ser usado para atribuir os valores de um Generator a uma matriz.

      // Create an array from the values of a Generator object
      const values = [...generator]
      
      console.log(values)
      

      Isso resultará na seguinte matriz:

      Output

      (3) ["Neo", "Morpheus", "Trinity"]

      Tanto a propagação quanto o for...of não usarão o return nos valores (neste caso, teria sido o 'The Oracle').

      Nota: embora ambos esses métodos sejam eficazes para trabalhar com o geradores finitos, se um gerador estiver lidando com um fluxo de dados infinito, não será possível usar a propagação ou o for...of diretamente sem criar um loop infinito.

      Fechando um gerador

      Como vimos, um gerador pode ter sua propriedade done definida como true e seu status como closed, por meio da iteração por todos os seus valores. Há duas maneiras adicionais de cancelar um gerador imediatamente: com o método return() e com o método throw().

      Com o return(), o gerador pode ser encerrado a qualquer momento, assim como seria se uma instrução return estivesse no corpo da função. É possível passar um argumento para return() ou deixá-lo em branco para um valor não definido.

      Para demonstrar o return(), vamos criar um gerador com alguns valores de yield, mas sem return na definição da função:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      }
      
      const generator = generatorFunction()
      

      O primeiro next() nos dará 'Neo', com done definido como false. Se invocarmos um método return() no objeto Generator logo após isso, vamos agora obter o valor passado e done definido como true. Qualquer chamada adicional para next() resultará na resposta de conclusão do gerador padrão com um valor não definido.

      Para demonstrar isso, execute os três métodos seguintes no generator:

      generator.next()
      generator.return('There is no spoon!')
      generator.next()
      

      Isso gerará os três resultados seguintes:

      Output

      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      O método return() forçou o objeto Generator a completar e a ignorar qualquer outra palavra-chave yield. Isso é particularmente útil na programação assíncrona quando você precisa fazer funções canceláveis, como interromper um pedido Web quando um usuário quer realizar uma ação diferente, uma vez que não é possível cancelar uma Promise diretamente.

      Se o corpo de uma função geradora tiver uma maneira de capturar e lidar com os erros, utilize o método throw() para lançar um erro no gerador. Isso inicia o gerador, lança o erro e termina o gerador.

      Para demonstrar isso, vamos colocar um try...catch dentro do corpo da função geradora e registrar um erro se algum for encontrado:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
          console.log(error)
        }
      }
      
      // Invoke the generator and throw an error
      const generator = generatorFunction()
      

      Agora, vamos executar o método next(), seguido do throw():

      generator.next()
      generator.throw(new Error('Agent Smith!'))
      

      Isso dará o seguinte resultado:

      Output

      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      Ao usar o throw(), injetamos um erro no gerador, que foi capturado pelo try...catch e registrado no console.

      Estados e métodos de objeto do gerador

      A tabela a seguir mostra uma lista de métodos que podem ser usados nos objetos Generator:

      Método Descrição
      next() Retorna o próximo valor em um gerador
      return() Retorna um valor em um gerador e termina o gerador.
      throw() Lança um erro e termina o gerador

      A tabela seguinte lista os possíveis estados de um objeto Generator:

      Status Descrição
      suspended O gerador parou a execução mas não terminou
      closed O gerador terminou por ter encontrado um erro, retornado ou iterado por todos os valores

      Delegação yield

      Além do operador de yield regular, os geradores podem usar também a expressão yield* para delegar outros valores para outro gerador. Quando o yield* for encontrado dentro de um gerador, ele entrará dentro do gerador delegado e começará a iterar por todos os yield até que esse gerador seja fechado. Isso pode ser usado para separar diferentes funções geradoras para organizar semanticamente seu código, ainda mantendo todos os seus yields iteráveis na ordem correta.

      Para demonstrar, podemos criar duas funções geradoras, uma das quais irá operar o yield* na outra:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      }
      
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()
      }
      

      Em seguida, vamos iterar pela função geradora begin():

      // Iterate through the outer generator
      const generator = begin()
      
      for (const value of generator) {
        console.log(value)
      }
      

      Isso dará os seguintes valores na ordem que eles são gerados:

      Output

      1 2 3 4

      O gerador exterior cedeu os valores 1 e 2, e então os delegou a outro gerador com yield*, que retornou 3 e 4.

      O yield* também pode delegar para qualquer objeto que seja iterável, como uma Matriz ou um Mapa. A delegação yield pode ser útil na organização do código, uma vez que qualquer função dentro de um gerador que quisesse usar o yield também teria que ser um gerador.

      Fluxo de dados infinito

      Um dos aspectos úteis dos geradores é a capacidade de trabalhar com fluxos e coleções de dados infinitos. Isso pode ser demonstrado criando um loop infinito dentro de uma função geradora que aumenta um número por um.

      No seguinte bloco de código, definimos essa função geradora e, em seguida, iniciamos o gerador:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
      
        while (true) {
          yield i++
        }
      }
      
      // Initiate the generator
      const counter = incrementer()
      

      Agora, itere pelos valores usando o next():

      // Iterate through the values
      counter.next()
      counter.next()
      counter.next()
      counter.next()
      

      Isso dará o seguinte resultado:

      Output

      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      A função retorna valores sucessivos no loop infinito, enquanto a propriedade done permanece falsa, garantindo que ela não terminará.

      Com os geradores, você não precisa se preocupar com a criação de um loop infinito, pois pode parar e retomar a execução quando quiser. No entanto, você ainda precisa ser cauteloso no que diz respeito à forma como invoca o gerador. Se usar a propagação ou o for...of em um fluxo de dados infinito, você ainda estará iterando sobre um loop infinito de uma vez, o que fará com que o ambiente falhe.

      Para um exemplo mais complexo de um fluxo de dados infinito, podemos criar uma função geradora de Fibonacci. A sequência de Fibonacci, que adiciona continuamente os dois valores anteriores somados, pode ser escrita usando um loop infinito dentro de um gerador da seguinte forma:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
      
        yield prev
        yield next
      
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
      
          yield newVal
      
          prev = next
          next = newVal
        }
      }
      

      Para testar isso, podemos rodar um loop por um número finito e imprimir a sequência do Fibonacci para o console.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      
      for (let i = 0; i < 10; i++) {
        console.log(fib.next().value)
      }
      

      Isso resultará no seguinte:

      Output

      0 1 1 2 3 5 8 13 21 34

      A capacidade de trabalhar com os conjuntos de dados infinitos é uma parte do que torna os geradores tão poderosos. Isso pode ser útil para exemplos como implementação de rolagem infinita no front-end de um aplicativo Web.

      Como passar valores aos geradores

      Ao longo deste artigo, usamos os geradores como iteradores e fornecemos valores em cada iteração. Além de produzir valores, os geradores podem também consumir valores a partir do next(). Neste caso, o yield terá um valor.

      É importante notar que o primeiro next() que é chamado não passará um valor. Ele apenas iniciará o gerador. Para demonstrar isso, podemos registrar o valor do yield e chamar o next() algumas vezes com alguns valores.

      function* generatorFunction() {
        console.log(yield)
        console.log(yield)
      
        return 'The end'
      }
      
      const generator = generatorFunction()
      
      generator.next()
      generator.next(100)
      generator.next(200)
      

      Isso dará o seguinte resultado:

      Output

      100 200 {value: "The end", done: true}

      Também é possível alimentar o gerador com um valor inicial. No exemplo a seguir, vamos fazer um loop for e passar cada valor no método next(), mas também vamos passar um argumento para a função inicial:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
        }
      }
      
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      
      for (let i = 0; i < 5; i++) {
        console.log(generator.next(i).value)
      }
      

      Vamos recuperar o valor do next() e fornecer um novo valor para a iteração seguinte, que é o valor anterior multiplicado por dez. Isso resultará no seguinte:

      Output

      0 10 20 30 40

      Outra maneira de lidar com a inicialização de um gerador é embrulhar o gerador em uma função que sempre irá chamar next() uma vez antes de fazer qualquer outra coisa.

      Uma função assíncrona é um tipo de função disponível no JavaScript ES6+ que torna mais fácil entender o trabalho com dados assíncronos, fazendo-o parecer síncrono. Os geradores têm uma matriz de capacidades mais extensa do que as funções assíncronas. No entanto, elas são capazes de replicar comportamentos semelhantes. Desta forma, a programação assíncrona pode aumentar a flexibilidade do seu código.

      Nesta seção, vamos demonstrar um exemplo de reprodução async/await com geradores.

      Vamos construir uma função assíncrona que usa a API Fetch para obter dados da API JSONPlaceholder (que fornece dados de exemplo JSON para teste) e registra a resposta no console.

      Comece definindo uma função assíncrona chamada getUsers que pega dados da API e retorna uma matriz de objetos e, em seguida, chama getUsers:

      const getUsers = async function() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        const json = await response.json()
      
        return json
      }
      
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))
      

      Isso resultará em dados JSON semelhantes aos seguintes:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Usando geradores, podemos criar algo quase idêntico que não usa as palavras-chave async/await. Em vez disso, você usará uma nova função que criamos e yield para fornecer valores em vez de usar await para esperar promises.

      No seguinte bloco de código, definimos uma função chamada getUsers que usa nossa nova função asyncAlt (a qual vamos escrever mais tarde) para imitar o async/await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('https://jsonplaceholder.typicode.com/users')
        const json = yield response.json()
      
        return json
      })
      
      // Invoking the function
      getUsers().then(response => console.log(response))
      

      Como podemos ver, ela parece quase idêntica à implantação async/await, exceto que há uma função geradora que está sendo passada e fornece valores.

      Agora, podemos criar uma função asyncAlt que se assemelha a uma função assíncrona. A asyncAlt tem uma função geradora como parâmetro, que é nossa função que fornece as promises que fetch retorna. A asyncAlt retorna uma função e resolve todas as promises que encontra até a última:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
      
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            }
      
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(generator.next(response))
            })
          }
      
          // Begin resolving promises
          return resolve(generator.next())
        }
      }
      

      Isso dará o mesmo resultado que a versão async/await:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Note que essa implantação tem como objetivo demonstrar como os geradores podem ser usados no lugar de async/await e não é um projeto pronto para a produção. Ele não tem o controle de erros configurado, nem tem a capacidade de passar os parâmetros para os valores fornecidos. Embora esse método possa adicionar flexibilidade ao seu código, muitas vezes o async/await será uma escolha melhor, uma vez que ele abstrai os detalhes de implantação e permite que você se concentre na escrita de código produtivo.

      Conclusão

      Os geradores são processos que podem parar e retomar a execução. Eles são uma funcionalidade poderosa e versátil do JavaScript, embora não sejam usados com frequência. Neste tutorial, aprendemos sobre funções geradoras e objetos geradores, métodos disponíveis para geradores, os operadores yield e yield* e os geradores usados com conjuntos de dados finitos e infinitos. Também exploramos uma maneira de implementar o código assíncrono sem callbacks aninhados ou cadeias de promessa longas.

      Se quiser aprender mais sobre a sintaxe do JavaScript, dê uma olhada em nossos tutoriais Entendendo This, Bind, Call e Apply no JavaScript e Entendendo os objetos Mapa e Conjunto no JavaScript.



      Source link

      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