roger-melo-treinamentos / curso-de-js-roger-melo

Repositório de informações do CJRM
491 stars 170 forks source link

Etapa 8, nome do vídeo: Correção dos exercícios da aula 01 - Aula 02-01 #287

Closed gcarniel closed 3 years ago

gcarniel commented 3 years ago

Oi, Roger. Minha dúvida é em relação ao exercício 21-05

Sei que objetos são passados como referência e estava fazendo esse exercício e não conseguia fazer uma cópia do objeto ao realizar o filter, mesmo colocando no return do filter {name, release} não conseguia o resultado desejado. Pois ao final da minha variável moviesBefore2000 eu trocava o nome e via que alterava também o objeto original.


const tarantinoMovies = [
  { name: 'Bastardos inglórios', release: 2009 },
  { name: 'Pulp Fiction', release: 1994 },
  { name: 'Kill Bill: Volume 2', release: 2004 },
  { name: 'Quatro Quartos', release: 1995 },
  { name: 'Sin City', release: 2005 },
  { name: 'Era uma Vez em... Hollywood', release: 2019 },
  { name: 'Django Livre', release: 2012 },
  { name: 'Cães de Aluguel', release: 1992 },
  { name: 'À Prova de Morte', release: 2007 },
  { name: 'Kill Bill: Volume 1', release: 2003 }
]

const moviesBefore2000 = tarantinoMovies .filter(({release })=> release < 2000)

moviesBefore2000[0].name = 'kkkkkkkkkkkk'
console.log('EX 05: ', moviesBefore2000,tarantinoMovies)

Então fui olhar os próximos exercícios e percebi que devia invocar o MAP após o filter e aí sim deu certo.

Minha dúvida é: Por que com o FILTER eu não consigo retornar uma cópia do objeto, tenho que fazer com o MAP?

Olhei no MDN mas não ficou claro e então resolvi te perguntar.

const moviesBefore2000 = tarantinoMovies
  .filter(({release })=> release < 2000)
  .map(({ name, release }) => ({ name, release }))

moviesBefore2000[0].name = 'kkkkkkkkkkkk'
console.log('EX 05: ', moviesBefore2000,tarantinoMovies)

@Roger-Melo

Roger-Melo commented 3 years ago

Olá Gabriel.

O filter deve ser usado apenas quando você precisa gerar um array com menos itens que o array original. A função de callback dele sempre deve retornar um boolean ou uma expressão que resulta em um boolean. É essa expressão que vai dizer se o item será incluído ou não no novo array que o filter está criando. Então em vias de regra, não é boa ideia fazer a função retornar algo que não seja um boolean ou uma expressão que resulta em um boolean.

O que está acontecendo no codigo abaixo é o seguinte...

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => release < 2000)

A cada vez que release < 2000 resulta em true, como true é um valor truthy, o objeto em questão é apenas referenciado no novo array que o filter está gerando.

Isso significa que tanto o item do index 1 do array original quanto o item do index 0 do novo array gerado pelo filter estão apontando pro mesmo objeto em memória:

const tarantinoMovies = [
  { name: 'Bastardos inglórios', release: 2009 },
  { name: 'Pulp Fiction', release: 1994 }
]

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => release < 2000)

console.log(tarantinoMovies[1] === moviesBefore2000[0]) // true

Dito isso, o que fez o map retornar novos objetos com cópias das propriedades dos objetos do array original foi a função de callback do map retornar o resultado da expressão ({ name, release }).

Quando você insere {}, está criando um novo objeto literal. Os parênteses ali só estão sendo usados para que {} não seja confundido com abertura e fechamento do bloco da função. Ou seja, a cada vez que a função de callback do map é executada, ela retorna um novo objeto que contém as propriedades name e release que recebem os valores das propriedades de mesmo nome dos objetos do array original.

Observe que se dentro da função de callback do map eu apenas retornar o objeto que ela recebeu por parâmetro, o mesmo objeto é referenciado nos dois arrays:

const tarantinoMovies = [
  { name: 'Bastardos inglórios', release: 2009 },
  { name: 'Pulp Fiction', release: 1994 }
]

const moviesBefore2000 = tarantinoMovies
  .filter(({ release }) => release < 2000)
  .map(obj => obj)

console.log(tarantinoMovies[1] === moviesBefore2000[0]) // true

Sacou a ideia?

@gcarniel

gcarniel commented 3 years ago

Saquei sim, @Roger-Melo

Mas o que não entendi é por que eu não posso fazer o filter retornar um novo objeto de acordo com o que foi filtrado?

const moviesBefore2000 = tarantinoMovies .filter(({ release }) => {
 if( release < 2000){
  ({ name, release })
 }
})
Roger-Melo commented 3 years ago

Primeiro, é importante entender que as funções de callback do map, filter e reduce sempre precisam retornar valores.

Então pra esse código funcionar, você teria que inserir um return antes do objeto. E nesse caso não precisa inserir o objeto nos parênteses:

const moviesBefore2000 = tarantinoMovies.filter(({ name, release }) => {
  if (release < 2000) {
    return { name, release }
  }
})

Os parênteses só são necessários quando você usa o retorno implícito da arrow function. Aí vc precisa envolver o objeto em parênteses:

// se não envolver o objeto em parênteses, a abertura e fechamento de chaves que vem depois da => serão consideradas abertura e fechamento do bloco da função
const moviesBefore2000 = tarantinoMovies.filter(({ name, release }) => { name, release })

// os parênteses executam a criação do objeto primeiro, e em seguida a função retorna o objeto
const moviesBefore2000 = tarantinoMovies.filter(({ name, release }) => ({ name, release }))

Nas primeiras etapas eu mostrei que expressões entre parênteses tem precedência em relação ao restante da expressão. Isso significa que essa expressão ({ name, release }) que foi inserida logo após a => da arrow function vai criar o objeto primeiro, e só então o retorno desse objeto será executado pela função.


Mas o que não entendi é por que eu não posso fazer o filter retornar um novo objeto de acordo com o que foi filtrado?

Por que ele não foi feito para isso. É o mesmo que usar uma furadeira para martelar um prego.

Vai funcionar, mas é uma ferramenta que tem outro propósito.

O filter não foi feito pra copiar objetos. Ele foi feito para criar um novo array com menos itens que o array original. Ponto.

Se os itens que o filter vai inserir no novo array são cópias ou não, tanto faz para ele.


Outro ponto que merece toda atenção: a cada retorno da função do filter, o que vai fazer o item ser inserido no novo array que o filter está criando é se a expressão ou valor retornado resulta em um valor truthy.

Isso significa que no código abaixo, o filter vai gerar um novo array com apenas os itenst 1 e 2:

const numbers = [0, 1, 2]
const truthyValues = numbers.filter(item => item)

console.log(truthyValues) // [1, 2]

Lembra que 0 é um valor falsy?

É exatamente isso que determinou que ele não fosse incluído no novo array.

Na primeira vez que item => item foi executada, ela retornou 0 (que é o 1º item do array). Como 0 é um valor falsy, 0 não foi inserido no array.

Na segunda vez que item => item foi executada, ela retornou 1 (que é o 2º item do array). Como 1 é um valor truthy, 1 foi inserido no novo array que o filter estava criando.

Na terceira e última vez que item => item foi executada, ela retornou 2 (que é o 3º item do array). Como 2 é um valor truthy, 2 foi inserido no novo array que o filter estava criando.

Considerando isso, observe agora o código abaixo:

const tarantinoMovies = [
  { release: 2009 },
  { release: 1994 }
]

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => {
  if (release < 2000) {
    return { release }
  }
})

console.log(moviesBefore2000) // [{ release: 1994 }]

Na primeira vez que a função foi executada, o bloco do if não foi executado. Porém, como eu disse antes, a função de callback do filter sempre precisa retornar um valor. Em JavaScript, toda função que não retorna um valor retorna undefined (por baixo dos panos).

Isso significa que por baixo dos panos, o que está acontecendo é isso:

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => {
  if (release < 2000) {
    return { release }
  }
  // undefined é retornado por baixo dos panos
  return undefined
})

Como undefined é um valor falsy, o objeto { release: 2009 } não foi inserido no novo array que o filter estava gerando.

Na segunda vez que a função foi executada, o bloco do if foi executado e { release } foi retornado. Só que o que fez { release } ser adicionado no novo array que é o fato de ele ser um objeto. Por que objeto é um valor truthy.

Ou seja, por baixo dos panos, o filter faz isso:

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => {
  if (release < 2000) {
    // o que o filter retorna é o resultado da expressão Boolean({ release })
    return Boolean({ release })
  }
})

Como a expressão Boolean({ release }) resulta em true e true é um valor truthy, o objeto { release: 1994 } foi adicionado no novo array que o filter gerou.

Devido a essa mecânica do filter, observe que com o código abaixo, o mesmo resultado é obtido:

const tarantinoMovies = [
  { release: 2009 },
  { release: 1994 }
]

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => release < 2000)

console.log(moviesBefore2000) // [{ release: 1994 }]

Na primeira vez que ({ release }) => release < 2000 foi executada, release < 2000 resultou em false e false foi retornado. Como false é um valor falsy, o objeto { release: 2009 } não foi adicionado no novo array que o filter estava criando.

Na segunda vez que ({ release }) => release < 2000 foi executada, release < 2000 resultou em true e true foi retornado. Como true é um valor truthy, o objeto { release: 1994 } foi adicionado no novo array que o filter estava criando.

É por isso que o if pode ser evitado. Você consegue filtrar os itens do array com um código mais conciso.

Está ficando mais claro?

@gcarniel

gcarniel commented 3 years ago

Olá, @Roger-Melo O funcionamento de cada um deles eu sei como funciona, através dos exercícios eu consegui fixar todo esse conteúdo que você acabou de passar.

Eu só não entendi por que eu não consigo retornar um novo objeto com filter mesmo passando um return { objeto } como está abaixo.

const tarantinoMovies = [
  { release: 2009 },
  { release: 1994 }
]

const moviesBefore2000 = tarantinoMovies.filter(({ release }) => {
  if (release < 2000) {
    return { release }
  }
})

moviesBefore2000[0].release = '9999'
console.log('moviesBefore2000', moviesBefore2000, tarantinoMovies)

Fiz o filter e retornei um "novo objeto", mas alterando o "novo" o antigo foi alterado, então não foi criado um novo.

Você disse acima que o filter não é pra isso e esta ok. Vou te explicar o que entendi na minha cabeça e usando FILTER e MAP como exemplos.

Sei que filter e map retornam um novo array, e partindo desse princípio eu poderia usar o filter em um array que contém objeto e retornar um novo objeto sem precisar encadear um map, já que ele retorna um novo array. Pois se o filter retorna um novo array com os elementos filtrados, teoricamente eu não precisaria invocar o map no resultado do filter se ele retornasse um novo objeto.

Mas na prática vi que ele retorna um novo array, mas para conseguir criar um novo objeto é só invocando o map após eu usar o filter ou usar o map e criar um novo objeto e nesse novo usar o filter.

o que entendi é que o filter retorna um novo array que satisfaça o que eu passei como callback pra ele, mas nunca vai me retornar um novo objeto mesmo eu passando explicitamente o return { novoObjeto }.

Não sei se consegui te passar minha dúvida, mas basicamente pra mim é se o filter já retorna um novo array filtrando o que quero, ele poderia no retorno criar um novo objeto.

Então na mihha cabeça ficou esse entendimento após exercícios e os testes:

Roger-Melo commented 3 years ago

Sempre tenha em mente que map e filter fazem coisas diferentes com os valores que eles retornam.

A grande sacada é que o exato valor que a função do map retorna é adicionado no novo array que o map está criando. Se o item do array é um objeto, se um novo objeto não for criado, o objeto original vai ser apenas referenciado no novo array:

const tarantinoMovies = [
  { release: 2009 }
]

const newArray = tarantinoMovies.map(item => item) // retornou uma referência do objeto original

console.log(tarantinoMovies[0] === newArray[0])
// retorna true pois { release: 2009 } foi apenas referenciado nos dois arrays

const newArray2 = tarantinoMovies.map(item => { 
  const { release } = item
  return { release } // retornou um novo objeto
})

console.log(tarantinoMovies[0] === newArray2[0])
// retorna false pois agora os dois arrays tem um objeto { release: 2009 }

Já no caso do filter, o valor que a função de callback dele retorna não será adicionado no novo array. O que vai acontecer no return da função do filter é: se o valor que a função retorna é truthy, o item do array que a função recebeu por parâmetro é adicionado no novo array. Caso contrário, o item não é adicionado.


const moviesBefore2000 = tarantinoMovies.filter(({ release }) => {
  if (release < 2000) {
    return { release } // criou um novo objeto
  }
})

No código acima, a expressão { release } criou um novo objeto.

Tanto é que se compararmos o objeto do array original com o objeto { release }, false é exibido pois são dois objetos criados em lugares diferentes na memória:

const moviesBefore2000 = tarantinoMovies.filter(originalObj => {
  const { release } = originalObj

  if (release < 2000) {
    const newObj = { release }

    console.log(originalObj, newObj) // {release: 1994} {release: 1994}
    console.log(originalObj === newObj) // false
    return newObj
  }
})

Só que não adianta retornar newObj com a intenção de adicioná-lo no array, por que no fim das contas, como newObj é um valor truthy, o que será adicionado no novo array do filter será o originalObj (recebido por parâmetro). Como objetos são tipos passados por referência, o objeto recebido por parâmetro será apenas referenciado no novo array.


  • Filter só consegue criar um novo array, não consegue retorna novo objeto.

Ele pode retornar um novo objeto (como no exemplo acima) ou qualquer outro valor, mas o valor retornado não deve ser usado com a intenção de ser adicionado no array, pois o que realmente importa no filter é se o valor retornado é truthy ou falsy.

  • Map consegue criar um novo array e também um novo objeto.

Sim. Ao contrário do filter, o valor que a função de callback do map retorna será inserido no novo array.


Está ficando mais claro?

@gcarniel

gcarniel commented 3 years ago

Saquei sim, @Roger-Melo Objetos são bastante complexos pelo fato de ser tipo de referência, tem que ter muito cuidado e atenção ao trabalhar com eles. Ainda bem que estou apanhando para eles, assim ajuda a fixar o conteúdo e saber como lidar com eles.

Obrigado pela explicação e dedicação em explicar, aliás, parabéns, você é fera.

Roger-Melo commented 3 years ago

Exatamente. Quem não erra, não aprende.

Você ainda irá treinar bastante o conceito de objetos serem tipos de referência no decorrer do treinamento =)

Já que ficou mais claro, vou fechar a issue. Mas no que eu puder ajudar, conte comigo. Rumo à fluência! 👊🏻

@gcarniel