Como Clonar um Objeto em JavaScript com Eficiência

Quando trabalhamos com frameworks JavaScript, clonar objetos é uma das atividades mais recorrentes que fazemos, e se não a fizermos de uma forma eficiente, ou da forma mais correta para cada caso, nossa aplicação paga um preço.

A resposta curta para como clonar objetos é simplesmente utilizar Object.assign({}, obj), para uma shallow copy e JSON.parse(JSON.stringify(obj)) para um deep clone do objeto.

Mas a resposta completa vai um pouco além e é difícil de colocar em apenas uma frase. Acompanhe abaixo:

O melhor método na minha opinião

O melhor método é quando você conhece a estrutura do objeto (99% dos casos eu diria), e pode fazer um simples for() em suas propriedades e ir compondo o objeto do seu modo, caso a velocidade seja algo fundamental.

É importante também saber a diferença entre shallow e deep clone (Veja mais abaixo) para a escolha do melhor método.

Caso você queira clonar um objeto que contém apenas strings e números, recomendo usar Object.assign(), veja mais abaixo em Shallow clone e seu método mais eficiente.

Mas para um deep clone genérico rápido e eficiente, o método a seguir eu encontrei na internet em um benchmark de JavaScript. Possivelmente este é o seu autor: https://github.com/c7x43t

O método original não incluía suporte a datas, então eu modifiquei um pouco a função adicionando esse suporte.

O método a seguir então é compatível com objetos que contem objetos, arrays, funções, datas, null, strings e números.

Veja a comparação da performance desta função aqui: http://jsben.ch/ligM2

file

Como a imagem acima mostra, a função deepClone leva apenas 30% do tempo do método stringify + parse.

const deepClone = obj => {
	// Se não for array ou objeto, retorna null
	if (typeof obj !== 'object' || obj === null) {
		return obj;
	}

	let cloned, i;

	// Handle: Date
	if (obj instanceof Date) {
		cloned = new Date(obj.getTime());
		return cloned;
	}

	// Handle: array
	if (obj instanceof Array) {
		let l;
		cloned = [];
		for (i = 0, l = obj.length; i < l; i++) {
			cloned[i] = deepClone(obj[i]);
		}

		return cloned;
	}

	// Handle: object
	cloned = {};
	for (i in obj) if (obj.hasOwnProperty(i)) {
		cloned[i] = deepClone(obj[i]);
	}

	return cloned;
}

const obj = { "name": "Ricardo Metring", "subobj": { color: "black" } };
const obj2 = deepClone(obj);

Shallow vs Deep Clone (Clonagem rasa vs profunda)

Em qualquer linguagem de programação, para clonarmos uma variável simples que contenha um número inteiro, basta fazermos a atribuição do valor à nova variável. Ex: var a = b;.

Entretanto, quando trabalhamos com estruturas de dados mais complexas, como um objeto em JavaScript, fazer apenas a atribuição à nova variável resulta em copiarmos apenas a referência da memória da variável copiada. Em outras palavras, se alterarmos o objeto "copiado", estamos alterando também o objeto na variável anterior.

Para clonarmos um objeto "a" em um objeto "b" de forma que possamos alterar o objeto "b" sem modificar o objeto "a", precisamos fazer uma clonagem, e esta clonagem pode ser tanto rasa (shallow) quanto profunda (deep). Mas qual a diferença entre shallow e deep clone, afinal? De fato, o próprio nome já indica o que cada uma significa, e para entendermos a diferença, precisamos de um objeto com mais de um nível de profundidade.

Shallow clone e seu método mais eficiente

Na clonagem rasa, apenas o primeiro nível dos atributos do objeto são de fato clonados. Já os objetos dentro dele não são clonados, tendo apenas as referências copiadas.

Isso significa que se eu alterar algum atributo de nível 2 ou superior, estarei alterando também o atributo do objeto original. Confuso? Veja o código abaixo para entender melhor.

Object.assign

Se o seu caso é apenas clonar um objeto que não contenha outras ramificações, como funções, objetos ou arrays, aconselho utilizar Object.assign().

const card = {
	"id": "1234567",
	"user": {
		"name": "Ricardo Metring"
	}
};

// Aqui fazemos a shallow copy da variável card
const card2 = Object.assign({}, card);

// Agora vamos alterar o conteúdo do objeto para fazermos um teste
card2.id = "2222222";
card2.user.name = "John Doe";

// Ao imprimir os valores, note que card.user.name foi alterado
console.log(card.id); // 1234567
console.log(card.user.name); // John Doe

console.log(card2.id); // 2222222
console.log(card2.user.name); // John Doe

Operador spread (...)

O operador spread é um recurso do ES6 que veio para facilitar muito a nossa vida. Esse operador sozinho merece um artigo dedicado aqui no site, pois serve pra muitas coisas, mas um dos seus principais usos é mesclar objetos.

Podemos então fazer uma cópia rasa com o operador spread da seguinte forma:

const obj2 = { ...obj };

Basicamente no código acima estamos criando um novo objeto e instruindo o JavaScript a listar todas as propriedades da variável obj dentro deste novo objeto.

Este método chegou a ser mais rápido que Object.assign em meus testes de benchmarking, porém se tratando de uma versão mais nova do JS ele não é compatível com alguns navegadores antigos, como Opera e Internet Explorer, então pela simplicidade do Object.assign dá quase na mesma usar um ou o outro.

Deep clone e outros métodos

Na clonagem profunda, obtemos uma nova cópia do objeto, juntamente com seus arrays, funções ou objetos internos que são todos como novas variáveis.

Existem vários métodos para criar uma cópia profunda de um objeto em JavaScript, mas a maioria delas necessita alguma dependência.

O meu método recomendado para o deep clone está mais acima no título O melhor método na minha opinião.

Citando então outros métodos:

jQuery

Se você já inclui o jQuery em suas dependências, pode usar o método extend(). Lembre-se que é preciso usar a flag true para que seja feito o deep clone. Não confunda este método com o clone() do jQuery que serve apenas para elementos DOM.

const obj = { "name": "Ricardo Metring", "num": 22 };
const obj2 = jQuery.extend(true, {}, obj);

lodash clone

Assim como no caso do jQuery, só recomendo usar este método caso já inclua a biblioteca lodash em seu projeto.

const obj = { "name": "Ricardo Metring", "num": 22 };
const obj2 = _.clone(obj, true);

JSON stringify e JSON parse

Este é um método bastante utilizado, pois não requer dependências externas. Ele funciona de uma forma que ao meu ver parece um pouco "gambiarra", mas ainda ok de se usar. Neste benchmark entretanto, ele acaba sendo o mais lento para mim no Chrome, porém um dos mais rápidos no Firefox. ?

É importante ressaltar que este método não é compatível com funções ou datas, pois JSON não é compatível com funções e datas são convertidas em string pelo JSON.stringify.

const obj = { "name": "Ricardo Metring", "num": 22 };
const obj2 = JSON.parse(JSON.stringify(obj));

Rolou até aqui procurando o Ctrl + C? Volte ao título "O melhor método na minha opinião".

Se você leu até aqui, agradeço pela paciência, você já sabe tudo sobre como clonar objetos em JavaScript!

Até mais!



Publicidade

Este artigo foi útil pra você?

Ricardo Metring

Ricardo Metring

Sou desenvolvedor full stack e co-fundador da Criar.io.
Trabalho há 10 anos com programação e sempre interessado em aprender mais.

Linkedin     Github

Artigos relacionados