View on GitHub

Capacitacao-CEOS-2-Javascript

Spread & Rest

Agora que vimos o que são e alguns métodos dos Arrays e dos Objetos, vamos falar um pouco sobre duas coisas muito úteis sobre. O operador spread e os parâmetros rest.


Spread

Sabemos que quando passamos um objeto em JavaScript para alguma função, temos uma passagem por referência, ou seja, toda e qualquer mudança realizada nesse objeto irá alterar o objeto que foi passado, ao invés do valor que foi passado.

Para ficar claro, veja a função abaixo:

function alteraPropA(objeto){
	objeto.a = "Fui alterado!";
	console.log(objeto.a);
}


Essa função recebe como parâmetro um objeto e o que ela faz é alterar a propriedade a desse objeto, além de claro, imprimí-la na tela.

Observe agora o código abaixo:

	let meuObj = {
		a: "Estou inalterado"
	}
	console.log(meuObj.a);
	// Imprimirá "Estou inalterado"

	alteraPropA(meuObj);
	// Imprimirá "Fui alterado!"

	console.log(meuObj.a);
	// Imprimirá "Fui alterado!"


Como podemos ver na execução, quando passamos meuObj como parâmetro na nossa função, não estamos passando apenas o valor do nosso objeto (como é feito com os números e Strings por exemplo), mas estamos passando o nosso próprio objeto na função. Isso significa que o objeto que definimos como meuObj e o objeto passado na função são exatamente os mesmos objetos, e toda alteração em um irá se refletir no outro.

Isso não é algo muito legal, já que muitas vezes isso pode gerar alguns resultados inesperados do nosso código.

Mas ora, então podemos simplementa fazer isso, certo?

	function alteraPropA(objeto){
		copia = objeto;
		copia.a = "Fui alterado!";
		console.log(copia.a);
	}

	let meuObj = {
		a: "Estou inalterado"
	}
	console.log(meuObj.a);

	alteraPropA(meuObj);

	console.log(meuObj.a);


Aparentemente isso deveria funcionar, certo? Execute o código acima e veja o seu resultado.

Inesperadamente (ou como esperado para alguns) vemos que possuímos a mesma saída de antes, ou seja, essa nossa solução não resolve o problema, pois o nosso operador de atribuição (=) ainda passa o objeto como referência.

Então como podemos resolver esse problema? Para isso temos o nosso lindo operador spread ().

O operador de spread copia todas as propriedades de um objeto e as retorna. Podemos usá-lo com as reticências () antes do nome do objeto.

Vamos ver na prática como ele funciona no exemplo abaixo:

	let a = {
		prop1: "valor1",
		prop2: "valor2",
		prop3: "valor3"
	}
	let b = a;
	console.log( b === a);
	// Irá imprimir true, pois eles são
	// exatamente o mesmo objeto


Agora usando nosso operador spread:

	let a = {
		prop1: "valor1",
		prop2: "valor2",
		prop3: "valor3"
	}
	let b = {...a};
	console.log( b === a);
	// Irá imprimir false, pois eles
	// não são o mesmo objeto, mesmo tendo as mesmas
	// propriedades e métodos com os mesmos valores


Isso fica claro para nós uma vez que percebemos que dois objetos em JavaScript (e em diversas linguagens de programação) são considerados os mesmos se eles estão armazenados na mesma posição de memória. Quando fazemos a atribuição direta com o operador = estamos dizendo: “Seu conteúdo é o desse endereço de memória”

Já quando usamos o spread, estamos dizendo: “Seu conteúdo é um objeto com as mesmas propriedades e métodos que esse, mas armazenado em um lugar diferente”

Por isso precisamos usar o operador envolvo em chaves, pois estamos definindo um novo objeto com as propriedades idênticas ao do outro objeto.

Como podemos resolver nosso problema da função acima com o operador spread? É bem simples na verdade, basta fazermos uma cópia do valor do nosso objeto com o spread e modificá-la:

	function alteraPropA(objeto){
		copia = {...objeto};
		copia.a = "Fui alterado!"
		console.log(copia.a);
	}

	let meuObj = {
		a: "Estou inalterado"
	}

	console.log(meuObj.a);
	// Imprimirá "Estou inalterado"

	alteraPropA(meuObj);
	// Imprimirá "Fui alterado!"

	console.log(meuObj.a);
	// Imprimirá "Estou inalterado"


Essa noção é estremamente importante quando precisamos fazer funções puras ou quando precisamos manter a imutabilidade dos objetos. Caso esses dois termos tenha despertado interesse em vocês, vale a pena a leitura dos links abaixo:

Funções Puras - SitePoint
Tempo médio de leitura: 10 minutos

Imutabilidade em JS - SitePoint
Tempo médio de leitura: 10 minutos

Mas atenção, a cópia das propriedades não é feita por valor, mas sim por referência! Então, tome muito cuidado ao copiar objetos que possuem propriedades que são outros objetos. Podemos ver isso no código abaixo:

	let a = {
		b: {
			c: "c",
			d: "d"
		},
		e: "e"
	}

	let copiaA = {...a};
	let copiaB = copiaA.b;
	let copiaBSpread = {...copiaA.b};

	console.log( a === copiaA);
	// Imprimirá false

	console.log( a.b === copiaB);
	// Imprimirá true, b não foi copiado por valor
	// Mas fim por referência

	console.log( a.b === copiaBSpread);
	// Imprimirá false, agora b foi copiado
	// por valor


Mas o nosso operador de spread não funciona apenas em objetos, também funciona muito bem em arrays, já que eles também possuem esse probleminha de serem passados como referência, e não como valor.

Utilizamos ele da mesma forma, tratando os nossos elementos do array como as propriedades do objeto, mas ao invés de usarmos ele entre chaves, usaremos entre colchetes:

	let a = [1,2,3,4];
	let copiaA = a;
	let copiaASpread = [...a];

	console.log(a === copiaA);
	// Imprimirá true, eles estão na mesma posição de memória

	console.log(a === copiaASpread);
	// Imprimirá false, são dois arrays de valor igual
	// mas armazenados em posições diferentes de memória


O operador de spread é especialmente útil quando precisamos juntar elementos de um array:

	let a = [1,2,3];
	let b = [4,5,6];
	
	let c = [...a, ...b];
	console.log(c);
	// Imprimirá: [1,2,3,4,5,6]


Basta ver o que está acontecendo, c recebe um array cujos elementos são todos os elemtentos de a e todos os elementos de b.

O operador de spread também é muito útil quando queremos fazer operações em uma array sem modificá-lo.

Você se lembra do método push do array? Ele adiciona um elemento no fim do array, mas isso modifica o nosso array, logo, se temos uma função:

	function previewDePush(array){
		array.push("Fui adicionado!");
		console.log( array );
	}

	let a = [1,2,3];
	previewDePush(a);
	// Imprimirá [1,2,3, "Fui adicionado!"]
	
	console.log(a);
	// Imprimirá [1,2,3, "Fui adicionado!"]


Se quisermos que a nossa função não modifique o array, apenas mostre como ele ficaria se fosse adicionado mais um elemento, podemos fazer como o nosso código em um objeto e fazer uma cópia usando o spread para não modificarmos nosso array original:

	function previewDePush(array){
		let copia = [...array];
		copia.push("Fui adicionado!");
		console.log( copia );
	}

	let a = [1,2,3];
	previewDePush(a);
	// Imprimirá [1,2,3, "Fui adicionado!"]
	
	console.log(a);
	// Imprimirá [1,2,3]


Isso é muito útil pelas mesmas razões que mostramos nos Objetos.

Rest

Existem tipos de parâmetros muito úteis também quando queremos fazer funções que realizem tarefas baseado no número de parâmetros que nos foi passado. Por exemplo, imagine uma função que você passa uma série de números e deve imprimir na tela a soma deles. Pode ser feita da seguinte forma:

function somatorio(arrayDeNumeros){
	let soma = 0;
	for(let i = 0; i< arrayDeNumeros.length; i++){
		soma += arrayDeNumeros[i];
	}
	console.log(soma);
}

somatorio([1,2,3]);
// Imprimirá 6


Trivial certo? Agora e se não quisermos passar um array? E se eu quiser passar de um jeito mais intuitivo, como por exemplo:

	somatorio(1,2,3);


Como podemos fazer isso? Como o nosso operador de rest.

O operador rest faz com que nossa função receba alguns dos seus parâmetros em forma de um array, desse modo, podemos fazer a chamada da função como mostrado acima. Para isso, basta usar o mesmo operador () no parâmetro que queremos utilizar:

function somatorio(...numeros){
	let soma = 0;
	for(let i = 0; i< numeros.length; i++){
		soma += numeros[i]
	}
	console.log(soma);
}

somatorio(1,2,3);
// Imprimirá 6


É mais uma ferramente que poderá ajudar em algumas situações onde você não quer se preocupar em transformar o parâmetro que irá passar em um array antes de chamar a função.

Mas atenção, para usarmos o parâmetro rest, ele precisa ser o último parâmetro da função, já que como o próprio nome diz, ele pega o restante dos parâmetros e joga eles em uma lista para que seja melhor de trabalhar com eles. Mas nada impede que passemos mais parâmetros antes dele:

function somatorio(saudacao, ...numeros){
	let soma = 0;
	for(let i = 0; i< numeros.length; i++){
		soma += numeros[i]
	}
	console.log(saudacao + soma);
}

somatorio("A soma é: ", 1,2,3);
// Imprimirá "A soma é: 6"


E é isso, você agora pode usar essas duas poderosas ferramentas para manipular Objetos e Arrays, sem mais ficar alterando sem querer um objeto que não deveria.