DEV Community

Renato Rodrigues
Renato Rodrigues

Posted on

Programação Funcional em JS, Imutabilidade e Reducers com Immer

Programação Funcional

Resumidamente podemos dizer que as linguagens que trabalham com o paradigma de programação funcional seguem alguns preceitos básicos:

  • Funções puras: Recebem as informações que precisam por parâmetros e retornam sempre o mesmo resultado se chamadas com os mesmos parâmetros;
  • Sem efeitos colaterais: As funções não mutam informações fora de seus escopos;
  • Stateless: As funções não possuem e não dependem de estados internos ou externos;
  • Idempotentes: As funções podem ser chamadas um número infinito de vezes e em diferentes contexto que resultado vai ser sempre o mesmo se os argumentos forem os mesmos, não gerando side-effects nem persistindo nenhuma alteração.

Programação Funcional em Javascript

Javascript por padrão não é uma linguagem funcional, porém por ser uma linguagem altamente flexível, adaptável e independente de paradigmas, os padrões de Programação Funcional podem ser utilizados em JS. É importante observar no entanto, que por ser uma linguagem extremamente permissiva e não te obrigar a praticamente nada, o Javascript não vai garantir que seu código siga todos os preceitos da PF. Fica a cargo do desenvolvedor aplicar esses preceitos corretamente.

Função não pura e com side effect em JS

var externalCount = 0;

function plus1() {
  externalCount = externalCount + 1;
  return externalCount;
}

console.log('External count:', externalCount); // External count: 0
var internalCount = plus1();
console.log('Internal count:', internalCount); // Internal count: 1
console.log('External count:', externalCount); // External count: 1
Enter fullscreen mode Exit fullscreen mode

Programação Funcional em JS antes do ES5

Antes do ES5 (ECMAScript 5th Edition) os conceitos de Programação Funcional praticamente não existiam em JS e só poderiam ser obtidos replicando manualmente esses comportamentos em seu código ou utilizando libs externas que faziam exatamente isso (ex, Underscore.js).

Programação Funcional em JS com ES5 (~ 2012*)

O ES5 trouxe para o mundo do JS alguns conceitos e funcionalidades de Programação Funcional, como por exemplo as funções map, filter e reduce, possibilitando aplicar esse novo paradigma onde antes não era possível. As revisões futuras do ES vieram a reforçar esse suporte.

* O ES5 foi oficialmente criado em 2009, mas só começou mesmo a ser utilizado por volta de 2012 devido à incompatibilidade com a base instalada de navegadores antigos.

Filtrando antes do ES5

var allNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

var evenNumbers = [];

for(var i = 0; i < allNumbers.length; i++) {
  if(allNumbers[i] % 2 === 0) {
    evenNumbers.push(allNumbers[i]);
  }
}

// evenNumbers = 0, 2, 4, 6, 8
Enter fullscreen mode Exit fullscreen mode

Filtrando com o ES5

var allNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

var evenNumbers = allNumbers.filter(function(number) {
  return number % 2 === 0
});

// evenNumbers = 0, 2, 4, 6, 8
Enter fullscreen mode Exit fullscreen mode

Imutabilidade

Imutabilidade em seu conceito mais básico diz que uma informação após ser definida não pode ser mais alterada.
Em programação isso traz algumas vantagens, como por exemplo garantir a consistência da informação durante todo o fluxo de dados, não gerar efeitos colaterais em funções, poder fazer referências à informação original ao invés de copiá-la, poder usar essa informação como índice ou chave, passá-la pra múltiplos threads e funções assíncronas, etc.

Imutabilidade em JS

Valor x referência

Em Javascript a maioria dos parâmetros são passados por referência, ou seja, ao invés de serem copiados, uma referência à informações original será criada. Isso significa que qualquer alteração feita nesse parâmetro refletirá também na informação original em si e vice-versa.

Tipos primitivos

A exceção à essa regra são os tipos primitivos da linguagem Javascript, que sempre são passados como valor, sem nenhuma referência à informação original. São eles:
Boolean, Number, String, Null, Undefined e Symbol.

Tipos Object

Tudo que não for um tipo primitivo em JS é do tipo Object e esse tipo sempre é passado por referência, portanto NÃO é imutável.

Tipos primitivos não mutam

var externalCount = 0;

function plus1(count) {
  count = count + 1;
  return count;
}

console.log('External count:', externalCount); // External count: 0
var internalCount = plus1(externalCount);
console.log('Internal count:', internalCount); // Internal count: 1
console.log('External count:', externalCount); // External count: 0
Enter fullscreen mode Exit fullscreen mode

Já os tipos não primitivos (Object) mutam

var externalCount = { 
  value: 0,
};

function plus1(count) {
  count.value = count.value + 1;
  return count;
}

console.log('External count:', externalCount.value); // External count: 0
var internalCount = plus1(externalCount);
console.log('Internal count:', internalCount.value); // Internal count: 1
console.log('External count:', externalCount.value); // External count: 1
Enter fullscreen mode Exit fullscreen mode

Então como obter imutabilidade em JS?

Pré ES5

Até o advento do ES5 era praticamente inexistente o conceito de imutabilidade em JS, pelo menos de forma nativa, tendo que recorrer a libs externas que de alguma maneira tentavam emular esse comportamento.

ES5

Object.assign

O ES5 introduziu o método assign aos objetos JS, que permite copiar o conteúdo de um número N de objetos à direita ao objeto mais à esquerda, mutando o mesmo e retornando o resultado. Note que somente o objeto mais à esquerda é mutado, os demais não.

Object.assign mutando o objeto mais à esquerda

var nameOnly = {
  name: 'Renato',
};

var lastNameOnly = {
  lastName: 'Rodrigues',
}

var emailOnly = {
  email: 'renato.rodrigues@renatorodrigues.com',
}

var myInfo = Object.assign(nameOnly, lastNameOnly, emailOnly);

// nameOnly = { name: 'Renato', lastName: 'Rodrigues', email: 'renato.rodrigues@renatorodrigues.com' }
// myInfo = { name: 'Renato', lastName: 'Rodrigues', email: 'renato.rodrigues@renatorodrigues.com' }
Enter fullscreen mode Exit fullscreen mode

Tá, mas e a imutabilidade? Lembrando que quando falamos de JS, a linguagem nos permite fazer praticamente qualquer coisa, temos então um pulo do gato aí. Em nenhum lugar é dito que o objeto mais à esquerda precisa ser uma referência a um objeto previamente criado. Ele pode ser simplesmente um objeto vazio, recém criado, que vai receber todos os valores à direita. Logo, se não existe uma referência pra ele, não existe o que mutar. ¯\(ツ)

Object.assign NÃO mutando o objeto mais à esquerda

var myInfo = Object.assign({}, nameOnly, lastNameOnly, emailOnly);

// nameOnly = { name: 'Renato' }
// myInfo = { name: 'Renato', lastName: 'Rodrigues', email: 'renato.rodrigues@renatorodrigues.com' }
Enter fullscreen mode Exit fullscreen mode

Importante: O Object.assign somente copia as propriedades de primeiro nível dos objetos em si, caso uma propriedade seja um objeto nested ou faça uma referência a um objeto externo elas poderão ser mutadas, pois continuarão a apontar para o objeto original.

ES6 (2015)

O ES6 (ECMAScript 6th Edition, a.k.a ES2015) trouxe mais algumas funções e funcionalidades relacionadas à imutabilidade, apesar de *spoiler* nenhuma delas garantir uma imutabilidade 100%.

Const

O ES6 trouxe ao JS o conceito de constantes, que em teoria são variáveis imutáveis, mas não se deixe enganar, o const só funciona assim com tipos primitivos. Com tipos Object ele não permite que você reatribua um novo valor à variável, mas continua permitindo que as propriedades de um objeto já atribuído a ela sejam alteradas.

Imutabilidade com Const

const name = 'Renato';

const person = {
  name: 'Renato',
}

name = 'Roberto'; // Error: Assignment to constant variable. 
person = { name: 'Roberto' }; // Error: Assignment to constant variable. 

person.name = 'Roberto' // person = { name: 'Roberto' }
Enter fullscreen mode Exit fullscreen mode

Object.seal

O Object.seal "sela" um objeto, não permitindo que nenhuma propriedade seja removida e nenhuma nova seja adicionada a ele, porém permite que suas propriedades já existentes sejam modificadas.

Importante: nenhum erro ou warning é retornado quando se tenta mutar um objeto selado.

const myInfo = {
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
};

Object.seal(myInfo);

delete myInfo.email;
myInfo.age = 90;
myInfo.name = "Roberto";

Enter fullscreen mode Exit fullscreen mode

Resultado quando não selado

myInfo = {
  name: 'Roberto',
  lastName: 'Rodrigues',
  age: 90,
}
Enter fullscreen mode Exit fullscreen mode

Resultado quando selado

myInfo = {
  name: 'Roberto',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
}
Enter fullscreen mode Exit fullscreen mode

Object.freeze

O Object.freeze "congela" um objeto, não permitindo que nenhuma propriedade seja removida, nenhuma nova seja adicionada a ele e nem que suas propriedades já existentes sejam modificadas.

Importante: nenhum erro ou warning é retornado quando se tenta mutar um objeto congelado.

const myInfo = {
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
};

Object.freeze(myInfo);

delete myInfo.email;
myInfo.age = 90;
myInfo.name = "Roberto";
Enter fullscreen mode Exit fullscreen mode

Resultado quando congelado

const myInfo = {
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
}
Enter fullscreen mode Exit fullscreen mode

"Ah mas então isso é imutabilidade!"

Assim como no Object.assign (e no Object.seal), o Object.freeze também só vale para as propriedades de primeiro nível do objeto em si, caso uma propriedade seja um objeto nested ou faça uma referência a um objeto externo seus filhos poderão ser mutados, pois esse objeto não estará congelado.

Objetos congelados com propriedades nested

const myInfo = {
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
  job: {
    title: "Self Employed",
    email: "renato@renatorodrigues.com",
  }
};

const jobAtSpaceX = {
  title: "VP of Mars Colonization",
  email: "renato.rodrigues@wearecoming.mars",
};

Object.freeze(myInfo);

myInfo.job = jobAtSpaceX;
myInfo.job.wage = undefined;
Enter fullscreen mode Exit fullscreen mode

Resultado obtido

myInfo = { 
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
  job: {
    title: 'Self Employed',
    email: 'renato.sp@gmail.com',
    wage: undefined 
  } 
}
Enter fullscreen mode Exit fullscreen mode

Objetos congelados com referência externa

const jobAtSpaceX = {
  title: "VP of Mars Colonization",
  email: "renato.rodrigues@wearecoming.mars",
};

const myInfo = {
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
  job: jobAtSpaceX,
};

Object.freeze(myInfo);

jobAtSpaceX.wage = Infinity;
Enter fullscreen mode Exit fullscreen mode

Resultado obtido

myInfo = { 
  name: 'Renato',
  lastName: 'Rodrigues',
  email: 'renato.rodrigues@renatorodrigues.com',
  job: {
    title: 'VP of Mars Colonization',
    email: 'renato.rodrigues@wearecoming.mars',
    wage: Infinity,
  } 
}
Enter fullscreen mode Exit fullscreen mode

ES6 (para Arrays) e E2018 (para Objetos)

Spread operator

O Spread operador se tornou o meio padrão de conseguir imutabilidade em JS, pois ao "espalhar" as propriedades de um array (ES6) ou objeto (ES2018) em um novo array ou objeto, uma cópia é feita, se tornando assim independente do original.

Mergeando objetos com o Spread

var nameOnly = {
  name: 'Renato',
};

var lastNameOnly = {
  lastName: 'Rodrigues',
}

var emailOnly = {
  email: 'renato.rodrigues@renatorodrigues.com',
}

var myInfo = { ...nameOnly, ...lastNameOnly, ...emailOnly };

// nameOnly = { name: 'Renato' }
// myInfo = { name: 'Renato', lastName: 'Rodrigues', email: 'renato.rodrigues@renatorodrigues.com' }
Enter fullscreen mode Exit fullscreen mode

"Ah, mas então está aí a imutabilidade 100% em JS"

O Spread Operator é como se fosse uma "versão moderna" do Object.assign({}, N). A mesma situação com propriedades nested e referências externas citadas anteriormente também ocorre com o spread.

Proxies

Uma das novidades mais interessantes (e menos utilizadas) do ES6 foram os proxies. Podemos dizer que quando se faz proxy de um objeto, ele fica "protegido" por um proxy que tem acesso à leitura e à gravação de suas propriedades e pode manipular como fazê-los. Resumindo, o proxy "esconde" o objeto original e qualquer acesso à ele é feito via métodos especiais chamados de traps. Isso pode ser utilizado juntamente com um padrão que favoreça a imutabilidade do objeto original, como por exemplo o copy-on-write.

Funcionamento prático do Proxy

Exemplo Proxy

const personObj = {
  firstName: 'Renato',
  lastName: 'Rodrigues',
}

const handler = {
  set(target, property, value) { // set Trap
    if (Array.isArray(value)) {
      target[property] = value.join(', ');
    } else {
      target[property] = value;
    }
  },
  get(target, property) { // get Trap
    if (typeof target[property] === 'string' && target[property].match(/, /)) {
      return target[property].split(', ');
    } else {
      return target[property];
    }
  }
}

const personProxy = new Proxy(personObj, handler);

personObj.hobbies = "video games, running";
personProxy.pets = ["Mario", "Luigi"];

// personObj.hobbies = "video games, running"
// personObj.pets = "Mario, Luigi"

// personProxy.hobbies = ["video games", "running"]
// personProxy.pets = ["Mario", "Luigi"]
Enter fullscreen mode Exit fullscreen mode

Immer

Fazendo o uso massivo de proxies, a biblioteca Immer surgiu como uma maneira mais simples e direta de garantir a imutabilidade de objetos em JS. Basicamente o que ela faz é proteger o objeto original (olha o Proxy aí \o/) e persistir cada alteração feita nele num objeto intermediário, chamado de Draft, para depois no final da operação produzir um novo objeto com o resultado das alterações do draft aplicadas ao objeto original.

Funcionamento prático da Immer

Mas como a Immer funciona?

Exemplo sem Immer (muta o objeto original)

const person = {
  firstName: 'Renato',
  lastName: 'Rodrigues',
}

const withCobli = (collaborator) => {
  collaborator.company = 'Cobli';
  return collaborator;
}

const employee = withCobli(person);

// person: { firstName: 'Renato', lastName: 'Rodrigues', company: 'Cobli' } 
// employee: { firstName: 'Renato', lastName: 'Rodrigues', company: 'Cobli' } 
Enter fullscreen mode Exit fullscreen mode

Exemplo com Spread (não muta o objeto original)

const person = {
  firstName: 'Renato',
  lastName: 'Rodrigues',
}

const withCobli = (collaborator) => {
  return {
    ...collaborator,
    company: 'Cobli',
  };
];

const employee = withCobli(person);

// person: { firstName: 'Renato', lastName: 'Rodrigues' } 
// employee: { firstName: 'Renato', lastName: 'Rodrigues', company: 'Cobli' } 
Enter fullscreen mode Exit fullscreen mode

Exemplo com Immer

const person = {
  firstName: 'Renato',
  lastName: 'Rodrigues',
}

const withCobli = produce(collaborator, draftCollaborator => {
  draftCollaborator.company = 'Cobli';
});

const employee = withCobli(person);

// person: { firstName: 'Renato', lastName: 'Rodrigues' } 
// employee: { firstName: 'Renato', lastName: 'Rodrigues', company: 'Cobli' } 
Enter fullscreen mode Exit fullscreen mode

Porque Immer?

De acordo com o artigo de introdução à Immer, somente na listagem do Redux-ecosystem-links existem outras 67 bibliotecas de imutabilidade em JS. Então porque usar a Immer?

  • Pequena: Somente 3kb e com ZERO dependências;
  • Rápida: Utiliza funções nativas de JS, como os proxies, ao invés de loops e condicionais;
  • Simples: Pode ser implementada em uma linhas e não requer nenhuma configuração;
  • Versátil: Suporta propriedades nested de infinitos níveis e mantem a tipagem correta das propriedades!
  • Fácil: Curva de aprendizagem praticamente zero, somente sintaxe nativa de JS, nenhuma outra sintaxe ou padrão é necessário;
  • Transparente: Uma vez implementada, você mal percebe que está usando;
  • Estruturalmente Equivalente: Partes não mutadas do novo objeto vão continuar fazendo referência ao objeto original. Mantendo eles assim equivalentes e evitando a duplicação de informações idênticas na memória.

Reducers

No mundo React existe um fluxo obrigatório de como as informações devem ser persistidas e lidas na Store (state). Esse fluxo é uni-direcional, com passos bem definidos e sempre trabalhando com imutabilidade (viu a chance aí?). Ele se aplica tanto ao estado da aplicação (usando Redux), quanto ao estado interno dos componentes (usando useReducer)

Um reducer nada mais é que uma função que recebe dois parâmetros, o estado atual e uma ação que contém um tipo e pode ou não conter um payload. A partir do tipo da ação é decidido quais mudanças serão aplicadas ao estado da aplicação/componente. Como este estado é imutável, esta função deve retornar uma nova cópia dele já com as diferenças aplicadas. Este então será o novo estado.

Hoje em dia, grande maioria dos devs utiliza o spread operator para fazer essa cópia do estado atual e modificá-lo sem mutá-lo

Reducer usando spread

const demoReducer = (state: demoState, action: demoActions): demoState => {
  switch (action.type) {
    case ActionTypes.INCREMENT_COUNTER:
      return {
        ...state,
        count: state.count + 1
      };
    case ActionTypes.DECREMENT_COUNTER:
      return {
        ...state,
        decrementCount: state.decrementCount - 1
      };
    case ActionTypes.SET_AMOUNT:
      return {
        ...state,
        amount: action.payload
      };
    default:
      return state;
  }
};
Enter fullscreen mode Exit fullscreen mode

Reducer usando spread (erro)

const demoReducer = (state: demoState, action: demoActions): demoState => {
  switch (action.type) {
    case ActionTypes.INCREMENT_COUNTER:
      return {
        ...state,
        count: state.count++
      };
  }
};
Enter fullscreen mode Exit fullscreen mode

Currying

Currying é o processo de transformar uma função que espera vários argumentos em uma função que espera um único argumento e retorna outra função que recebe um argumento e assim sucessivamente até o final dos argumentos. Por exemplo, uma função que recebe três argumentos, vai resultar em uma função que recebe um argumento e retorna uma função que recebe um argumento, que por sua vez retorna uma função que recebe um argumento e retorna o resultado da função original.

As funções curried podem tanto receber parâmetros das funções acima quanto os valores retidos nessas funções (closures)

Exemplo Currying

function sayTo(who) {
  const person = who.toString().toUpperCase();

  return function say(what) {
    console.log(`Say ${what} to ${person}`)
  }
}

const sayToRenato = sayTo('Renato');
sayToRenato('Wasuuuup!'); 
// Say Wasuuuup! to RENATO 

const sayToCaio = sayTo('Caio');
sayToCaio('Yo Bro!')
sayToCaio('Whats going on?');
// Say Yo Bro! to CAIO
// Say Whats going on? to CAIO 
Enter fullscreen mode Exit fullscreen mode

Curried Reducer

Aproveitando as vantagens do currying, os desenvolvedores da Immer criaram o modo curried do método produce, que cai como uma luva quando usado em um Reducer. Ele recebe como o único parâmetro uma função de callback que vai receber um state e uma action. O curried reducer irá retornar uma função que ao receber o state original irá criar um draft dele e chamará o callback passando esse draft junto com a action passada pelo Redux/useReducer, criando assim um reducer 100% compatível e completamente transparente para quem o chama.

Exemplo Curried Reducer

import produce, { Draft } from "immer";

const demoReducer = produce((draft: Draft<DemoState>, action: DemoActions) => {
  switch (action.type) {
    case ActionTypes.INCREMENT_COUNTER:
      draft.count++;
      break;
    case ActionTypes.DECREMENT_COUNTER:
      draft.decrementCount--;
      break;
    case ActionTypes.SET_AMOUNT:
      draft.amount = action.payload;
      break;
  }
});
Enter fullscreen mode Exit fullscreen mode

(Sim, é só isso que precisa pra usar, não tem mais nada mesmo)

Como migrar para Immer

1) Importe a função produce da Immer;
2) Adicione-a no modo curried ao seu reducer ja existente;
3) Remova todos os returns;
4) Remova todos os spreads;
5) Mute as propriedades diretamente;
6) Remova o case default.

Desafios

1) Esquecer o padrão spread;
2) (Re-)aprender a mutar propriedades de um objeto usando JS básico.

Na dúvida sempre lembre-se

Bonus: Mutações básicas

// Alterar propriedade
draft.property = newValue;

// Criar propriedade
draft.newProperty = value;

// Excluir propriedade
delete draft.uselessProperty;

// Alterar/criar propriedade nested
draft.nestedObject.nestedProperty = value; 

// Excluir propriedade nested
delete draft.nestedObject.uselessNestedProperty;

// Alterar/criar propriedade nested em um array
draft.nestedArray[i].nestedProperty = value;

// Adicionar um item ao um array
draft.nestedArray.push(newObj);

// Excluir propriedade nested em um array
delete draft.nestedArray[i].uselessNestedProperty;

// Excluir um item ao um array
draft.nestedArray.splice(i, 1);
Enter fullscreen mode Exit fullscreen mode

Referência completa de mutação em objetos e arrays

FAQ

Preciso migrar tudo?

Nop. A menos que queira.

Qual o melhor momento para migrar então?

Considerando que a migração é bem simples, eu usaria a seguinte regra: Tem que modificar algo num Reducer? já aproveita a chance e migra.

Ideias para o futuro

  • Adicionar Reducers com Immer ao gerador de pacotes. (done)
  • <insira sua ideia aqui>

Exemplos de reducers migrados

Apêndice 1: Igualdade de valores e referências

Igualdade de valores e referências

var allNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

var evenNumbers = [];

for (var i = 0; i < allNumbers.length; i++) {
  if (allNumbers[i] % 2 === 0) {
    evenNumbers.push(allNumbers[i]);
  }
}

evenNumbers == allNumbers; // false
evenNumbers[0] === allNumbers[0]; // true
evenNumbers[1] === allNumbers[2]; // true
Enter fullscreen mode Exit fullscreen mode

Igualdade de valores e referências usando filter

var allNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

var evenNumbers = allNumbers.filter(function (number) {
  return number % 2 === 0
});

evenNumbers == allNumbers; // false
evenNumbers[0] === allNumbers[0]; // true
evenNumbers[1] === allNumbers[2]; // true
Enter fullscreen mode Exit fullscreen mode

Igualdade de valores e referências em objetos

  var allNumbers = [ 
    { v : 0 }, { v : 1 }, { v : 2 }, { v : 3 }, { v : 4 }, 
    { v : 5 }, { v : 6 }, { v : 7 }, { v : 8 }, { v : 9 }
  ];

  var evenNumbers = allNumbers.filter(function (item) {
      return item.v % 2 === 0
  });

  evenNumbers == allNumbers; // false
  evenNumbers[0] === allNumbers[0]; // true
  evenNumbers[0].v === allNumbers[0].v; // true
  evenNumbers[1] === allNumbers[2]; // true
  evenNumbers[1].v === allNumbers[2].v; // true
Enter fullscreen mode Exit fullscreen mode

Igualdade de valores e referências com Object.assign

var nameOnly = {
  name: 'Renato',
};

var lastNameOnly = {
  lastName: 'Rodrigues',
}

var emailOnly = {
  email: 'renato.rodrigues@renatorodrigues.com',
}

var myInfo = Object.assign(nameOnly, lastNameOnly, emailOnly);

myInfo.name === nameOnly.name; // true
myInfo.email === nameOnly.email; // true
myInfo === nameOnly; // true
Enter fullscreen mode Exit fullscreen mode

Igualdade de valores e referências com Object.assign({})

var nameOnly = {
  name: 'Renato',
};

var lastNameOnly = {
  lastName: 'Rodrigues',
}

var emailOnly = {
  email: 'renato.rodrigues@renatorodrigues.com',
}

var myInfo = Object.assign({}, nameOnly, lastNameOnly, emailOnly);

myInfo.name === nameOnly.name; // true
myInfo.email === nameOnly.email; // false
myInfo === nameOnly; // false
Enter fullscreen mode Exit fullscreen mode

Igualdade de valores e referências com o spread operator

var nameOnly = {
  name: 'Renato',
};

var lastNameOnly = {
  lastName: 'Rodrigues',
}

var emailOnly = {
  email: 'renato.rodrigues@renatorodrigues.com',
}

var myInfo = { ...nameOnly, ...lastNameOnly, ...emailOnly };

myInfo.name === nameOnly.name; // true
myInfo.email === nameOnly.email; // false
myInfo === nameOnly; // false
Enter fullscreen mode Exit fullscreen mode

Equality Operator (==) e Strict Equality Operador (===)

Para tipos primitivos

== Compara igualdade de valores (faz conversões)

=== Compara igualdade de valores e tipo (não faz conversões)

"1" == 1; // true
"1" === 1; // false
1 == true); // true
1 === true); // false
"" == false); // true
"" === false); // false
[] == false); // true
[] === false); // false
undefined == null); // true
undefined === null); // false
"" == undefined); // false
"" === undefined); // false
Enter fullscreen mode Exit fullscreen mode

Para tipos Object

== Compara referência (ignora estrutura e valores)

=== Compara referência (ignora estrutura e valores)

var person = {
  name: 'Renato',
}

var person2 = {
    name: 'Renato',
}

var person3 = person;

person == person2; // false
person === person2; // false
person == person3; // true
person === person3; // true
Enter fullscreen mode Exit fullscreen mode

Bonus: Igualdade de NaN

const a = NaN;
const b = NaN;

a == NaN; // false
b == NaN; // false
a == b; // false
NaN == NaN; // false

Object.is(a, b); // true
Object.is(NaN, NaN); // true
Enter fullscreen mode Exit fullscreen mode

Top comments (0)